Skip to content

Commit 1e8d497

Browse files
committed
allow serving static files from the database
serve static files from the virtual filesystem
1 parent e1c5d80 commit 1e8d497

File tree

5 files changed

+61
-77
lines changed

5 files changed

+61
-77
lines changed

Cargo.lock

Lines changed: 1 addition & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ actix-web = { version = "4", features = ["rustls"] }
1616
handlebars = "5.0.0-beta.0"
1717
log = "0.4.17"
1818
env_logger = "0.10.0"
19-
actix-files = "0.6.2"
19+
mime_guess = "2.0.4"
2020
futures-util = "0.3.21"
2121
dashmap = "5.4.0"
2222
tokio = { version = "1.24.1", features = ["rt"] }

src/filesystem.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use anyhow::Context;
44
use chrono::{DateTime, Utc};
55
use sqlx::any::{AnyKind, AnyStatement, AnyTypeInfo};
66
use sqlx::postgres::types::PgTimeTz;
7-
use sqlx::{Executor, Postgres, Statement, Type};
7+
use sqlx::{Postgres, Statement, Type};
88
use std::io::ErrorKind;
99
use std::path::{Component, Path, PathBuf};
1010

@@ -77,10 +77,12 @@ impl FileSystem {
7777
}
7878

7979
fn safe_local_path(&self, path: &Path) -> anyhow::Result<PathBuf> {
80-
anyhow::ensure!(
81-
path.components().all(|c| matches!(c, Component::Normal(_))),
82-
"Unsupported path: {path:?}"
83-
);
80+
for component in path.components() {
81+
anyhow::ensure!(
82+
matches!(component, Component::Normal(_)),
83+
"Unsupported path: {path:?}. Path component {component:?} is not allowed."
84+
);
85+
}
8486
Ok(self.local_root.join(path))
8587
}
8688
}
@@ -165,6 +167,7 @@ impl DbFsQueries {
165167

166168
#[actix_web::test]
167169
async fn test_sql_file_read_utf8() -> anyhow::Result<()> {
170+
use sqlx::Executor;
168171
let state = AppState::init().await?;
169172
state
170173
.db

src/main.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use std::net::{SocketAddr, ToSocketAddrs};
1717
use std::path::PathBuf;
1818
use templates::AllTemplates;
1919

20-
const CONFIG_DIR: &str = "sqlpage";
2120
const TEMPLATES_DIR: &str = "sqlpage/templates";
2221
const MIGRATIONS_DIR: &str = "sqlpage/migrations";
2322

@@ -26,7 +25,6 @@ const DEFAULT_DATABASE_FILE: &str = "sqlpage.db";
2625
pub struct AppState {
2726
db: Database,
2827
all_templates: AllTemplates,
29-
web_root: PathBuf,
3028
sql_file_cache: FileCache<ParsedSqlFile>,
3129
file_system: FileSystem,
3230
}
@@ -44,7 +42,6 @@ impl AppState {
4442
Ok(AppState {
4543
db,
4644
all_templates,
47-
web_root,
4845
sql_file_cache,
4946
file_system,
5047
})

src/webserver/http.rs

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use crate::render::{HeaderContext, PageContext, RenderContext};
22
use crate::webserver::database::{stream_query_results, DbItem};
3-
use crate::{AppState, Config, ParsedSqlFile, CONFIG_DIR};
4-
use actix_web::dev::{ServiceFactory, ServiceRequest};
3+
use crate::{AppState, Config, ParsedSqlFile};
4+
use actix_web::dev::{fn_service, ServiceFactory, ServiceRequest};
55
use actix_web::error::ErrorInternalServerError;
6-
use actix_web::http::header::{CacheControl, CacheDirective};
6+
use actix_web::http::header::{CacheControl, CacheDirective, ContentType};
77
use actix_web::web::Form;
88
use actix_web::{
9-
dev::Service, dev::ServiceResponse, middleware, middleware::Logger, web, web::Bytes, App,
10-
FromRequest, HttpResponse, HttpServer, Responder,
9+
dev::ServiceResponse, middleware, middleware::Logger, web, web::Bytes, App, FromRequest,
10+
HttpResponse, HttpServer, Responder,
1111
};
1212

1313
use crate::utils::log_error;
@@ -19,7 +19,7 @@ use std::collections::HashMap;
1919
use std::io::Write;
2020
use std::mem;
2121
use std::net::IpAddr;
22-
use std::path::{Component, PathBuf};
22+
use std::path::PathBuf;
2323
use std::pin::Pin;
2424
use std::sync::Arc;
2525
use tokio::sync::mpsc;
@@ -348,6 +348,38 @@ async fn handle_static_js() -> impl Responder {
348348
.body(&include_bytes!("../../sqlpage/sqlpage.js")[..])
349349
}
350350

351+
async fn serve_file(path: &str, state: &AppState) -> actix_web::Result<HttpResponse> {
352+
let path = path.strip_prefix('/').unwrap_or(path);
353+
state
354+
.file_system
355+
.read_file(state, path.as_ref())
356+
.await
357+
.map_err(actix_web::error::ErrorBadRequest)
358+
.map(|b| {
359+
HttpResponse::Ok()
360+
.insert_header(
361+
mime_guess::from_path(path)
362+
.first()
363+
.map(ContentType)
364+
.unwrap_or(ContentType::octet_stream()),
365+
)
366+
.body(b)
367+
})
368+
}
369+
370+
async fn main_handler(mut service_request: ServiceRequest) -> actix_web::Result<ServiceResponse> {
371+
let path = service_request.path();
372+
let sql_file_path = path_to_sql_file(path);
373+
if let Some(sql_path) = sql_file_path {
374+
process_sql_request(service_request, sql_path).await
375+
} else {
376+
let app_state = service_request.extract::<web::Data<AppState>>().await?;
377+
let path = service_request.path();
378+
let response = serve_file(path, &app_state).await?;
379+
Ok(service_request.into_response(response))
380+
}
381+
}
382+
351383
pub fn create_app(
352384
app_state: web::Data<AppState>,
353385
) -> App<
@@ -360,32 +392,19 @@ pub fn create_app(
360392
>,
361393
> {
362394
App::new()
363-
.route("sqlpage.js", web::get().to(handle_static_js))
364-
.wrap_fn(|req, srv| {
365-
let sql_file_path = path_to_sql_file(req.path());
366-
let sql_file = if let Some(sql_path) = sql_file_path {
367-
Ok((req, sql_path))
368-
} else {
369-
Err(srv.call(req))
370-
};
371-
async move {
372-
match sql_file {
373-
Ok((req, sql_path)) => process_sql_request(req, sql_path).await,
374-
Err(fallback) => fallback.await
375-
}
376-
}
377-
})
378-
.default_service(
379-
actix_files::Files::new("/", &app_state.web_root)
380-
.path_filter(|path, _|
381-
!matches!(path.components().next(), Some(Component::Normal(x)) if x == CONFIG_DIR))
382-
.show_files_listing()
383-
.use_last_modified(true),
384-
)
385-
.wrap(Logger::default())
386-
.wrap(middleware::DefaultHeaders::new()
387-
.add(("Server", format!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))))
388-
.add(("Content-Security-Policy", "script-src 'self' https://cdn.jsdelivr.net"))
395+
.route("sqlpage.js", web::get().to(handle_static_js))
396+
.default_service(fn_service(main_handler))
397+
.wrap(Logger::default())
398+
.wrap(
399+
middleware::DefaultHeaders::new()
400+
.add((
401+
"Server",
402+
format!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")),
403+
))
404+
.add((
405+
"Content-Security-Policy",
406+
"script-src 'self' https://cdn.jsdelivr.net",
407+
)),
389408
)
390409
.wrap(middleware::Compress::default())
391410
.app_data(app_state)

0 commit comments

Comments
 (0)