Skip to content

Commit 50ad903

Browse files
committed
fix #89
1 parent ff39414 commit 50ad903

File tree

5 files changed

+55
-24
lines changed

5 files changed

+55
-24
lines changed

sqlpage/sqlpage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"database_url": "sqlite://./sqlpage.db?mode=rwc",
3-
"listen_on": "localhost:8081"
3+
"listen_on": "localhost:8080"
44
}

src/file_cache.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ impl<T: AsyncFromStrWithState> FileCache<T> {
101101
}
102102
match app_state
103103
.file_system
104-
.modified_since(app_state, path, cached.last_check_time())
104+
.modified_since(app_state, path, cached.last_check_time(), true)
105105
.await
106106
{
107107
Ok(false) => {
@@ -115,7 +115,10 @@ impl<T: AsyncFromStrWithState> FileCache<T> {
115115
}
116116
// Read lock is released
117117
log::trace!("Loading and parsing {:?}", path);
118-
let file_contents = app_state.file_system.read_to_string(app_state, path).await;
118+
let file_contents = app_state
119+
.file_system
120+
.read_to_string(app_state, path, true)
121+
.await;
119122

120123
let parsed = match file_contents {
121124
Ok(contents) => {

src/filesystem.rs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use chrono::{DateTime, Utc};
66
use sqlx::any::{AnyKind, AnyStatement, AnyTypeInfo};
77
use sqlx::postgres::types::PgTimeTz;
88
use sqlx::{Postgres, Statement, Type};
9+
use std::borrow::Cow;
910
use std::io::ErrorKind;
1011
use std::path::{Component, Path, PathBuf};
1112

@@ -37,8 +38,13 @@ impl FileSystem {
3738
app_state: &AppState,
3839
path: &Path,
3940
since: DateTime<Utc>,
41+
priviledged: bool,
4042
) -> anyhow::Result<bool> {
41-
let local_path = self.safe_local_path(path)?;
43+
let local_path = if priviledged {
44+
Cow::Borrowed(path)
45+
} else {
46+
Cow::Owned(self.safe_local_path(path)?)
47+
};
4248
let local_result = file_modified_since_local(&local_path, since).await;
4349
match (local_result, &self.db_fs_queries) {
4450
(Ok(modified), _) => Ok(modified),
@@ -58,14 +64,27 @@ impl FileSystem {
5864
&self,
5965
app_state: &AppState,
6066
path: &Path,
67+
priviledged: bool,
6168
) -> anyhow::Result<String> {
62-
let bytes = self.read_file(app_state, path).await?;
69+
let bytes = self.read_file(app_state, path, priviledged).await?;
6370
String::from_utf8(bytes)
6471
.with_context(|| format!("The file at {path:?} contains invalid UTF8 characters"))
6572
}
6673

67-
pub async fn read_file(&self, app_state: &AppState, path: &Path) -> anyhow::Result<Vec<u8>> {
68-
let local_path = self.safe_local_path(path)?;
74+
/**
75+
* Priviledged files are the ones that are in sqlpage's config directory.
76+
*/
77+
pub async fn read_file(
78+
&self,
79+
app_state: &AppState,
80+
path: &Path,
81+
priviledged: bool,
82+
) -> anyhow::Result<Vec<u8>> {
83+
let local_path = if priviledged {
84+
Cow::Borrowed(path)
85+
} else {
86+
Cow::Owned(self.safe_local_path(path)?)
87+
};
6988
let local_result = tokio::fs::read(&local_path).await;
7089
match (local_result, &self.db_fs_queries) {
7190
(Ok(f), _) => Ok(f),
@@ -82,11 +101,16 @@ impl FileSystem {
82101
}
83102

84103
fn safe_local_path(&self, path: &Path) -> anyhow::Result<PathBuf> {
85-
for component in path.components() {
86-
anyhow::ensure!(
87-
matches!(component, Component::Normal(_)),
88-
"Unsupported path: {path:?}. Path component {component:?} is not allowed."
89-
);
104+
for (i, component) in path.components().enumerate() {
105+
if let Component::Normal(c) = component {
106+
if c.eq_ignore_ascii_case("sqlpage") && i == 0 {
107+
anyhow::bail!("Access to the sqlpage config directory is not allowed.");
108+
}
109+
} else {
110+
anyhow::bail!(
111+
"Unsupported path: {path:?}. Path component '{component:?}' is not allowed."
112+
);
113+
}
90114
}
91115
Ok(self.local_root.join(path))
92116
}
@@ -202,7 +226,7 @@ async fn test_sql_file_read_utf8() -> anyhow::Result<()> {
202226
.await?;
203227
let fs = FileSystem::init("/", &state.db).await;
204228
let actual = fs
205-
.read_to_string(&state, "unit test file.txt".as_ref())
229+
.read_to_string(&state, "unit test file.txt".as_ref(), false)
206230
.await?;
207231
assert_eq!(actual, "Héllö world! 😀");
208232
Ok(())

src/webserver/http.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ async fn serve_file(
439439
let since = DateTime::<Utc>::from(SystemTime::from(date));
440440
let modified = state
441441
.file_system
442-
.modified_since(state, path.as_ref(), since)
442+
.modified_since(state, path.as_ref(), since, false)
443443
.await
444444
.map_err(actix_web::error::ErrorBadRequest)?;
445445
if !modified {
@@ -448,7 +448,7 @@ async fn serve_file(
448448
}
449449
state
450450
.file_system
451-
.read_file(state, path.as_ref())
451+
.read_file(state, path.as_ref(), false)
452452
.await
453453
.map_err(actix_web::error::ErrorBadRequest)
454454
.map(|b| {

tests/index.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,27 @@ use sqlpage::{app_config::AppConfig, webserver::http::main_handler, AppState};
66

77
#[actix_web::test]
88
async fn test_index_ok() {
9+
let resp = req_path("/").await;
10+
assert_eq!(resp.status(), http::StatusCode::OK);
11+
let body = test::read_body(resp).await;
12+
assert!(body.starts_with(b"<!DOCTYPE html>"));
13+
// the body should contain the strint "It works!" and should not contain the string "error"
14+
let body = String::from_utf8(body.to_vec()).unwrap();
15+
assert!(body.contains("It works !"));
16+
assert!(!body.contains("error"));
17+
}
18+
19+
async fn req_path(path: &str) -> actix_web::dev::ServiceResponse {
920
init_log();
1021
let config = test_config();
1122
let state = AppState::init(&config).await.unwrap();
1223
let data = actix_web::web::Data::new(state);
1324
let req = test::TestRequest::get()
14-
.uri("/")
25+
.uri(path)
1526
.app_data(data)
1627
.insert_header(ContentType::plaintext())
1728
.to_srv_request();
18-
let resp = main_handler(req).await.unwrap();
19-
assert_eq!(resp.status(), http::StatusCode::OK);
20-
let body = test::read_body(resp).await;
21-
assert!(body.starts_with(b"<!DOCTYPE html>"));
22-
// the body should contain the strint "It works!" and should not contain the string "error"
23-
let body = String::from_utf8(body.to_vec()).unwrap();
24-
assert!(body.contains("It works !"));
25-
assert!(!body.contains("error"));
29+
main_handler(req).await.unwrap()
2630
}
2731

2832
pub fn test_config() -> AppConfig {

0 commit comments

Comments
 (0)