v0.33.0
This commit is contained in:
1 parent
bbfa8cbc3f
commit
2ccc598b12
49 files changed
+4771
-639
No files matched your search
+101
-40
@@ -1,5 +1,7 @@
|
||||
use super::{
|
||||
fastcgi::{method_not_allowed_response, php_for_path, php_response, protected_script_without_handler},
|
||||
fastcgi::{
|
||||
method_not_allowed_response, php_for_path, php_response, protected_script_without_handler,
|
||||
},
|
||||
filesystem::{list_directory_sync, path_components},
|
||||
htaccess,
|
||||
response::{
|
||||
@@ -59,7 +61,10 @@ async fn serve_inner(
|
||||
context: RequestContext,
|
||||
) -> Response {
|
||||
if crate::acl::validate_decoded_routing_path(path).is_err() {
|
||||
return status_response(StatusCode::BAD_REQUEST, "RustProxy: invalid Web Server path\n");
|
||||
return status_response(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"RustProxy: invalid Web Server path\n",
|
||||
);
|
||||
}
|
||||
if hardening_denied_method(service, &method) || hardening_denied_path(service, path) {
|
||||
return builtin_error_response(StatusCode::FORBIDDEN, &method);
|
||||
@@ -68,8 +73,11 @@ async fn serve_inner(
|
||||
return method_not_allowed_response();
|
||||
}
|
||||
|
||||
if path_components(path).ok().is_some_and(|components| components.iter().any(|component| htaccess::protected_component(service.htaccess.as_ref(), component)))
|
||||
|| htaccess::denied(service.htaccess.as_ref(), path)
|
||||
if path_components(path).ok().is_some_and(|components| {
|
||||
components
|
||||
.iter()
|
||||
.any(|component| htaccess::protected_component(service.htaccess.as_ref(), component))
|
||||
}) || htaccess::denied(service.htaccess.as_ref(), path)
|
||||
{
|
||||
return builtin_error_response(StatusCode::FORBIDDEN, &method);
|
||||
}
|
||||
@@ -93,15 +101,30 @@ async fn serve_inner(
|
||||
&context,
|
||||
&rewritten_path,
|
||||
rewritten_query.as_deref(),
|
||||
).await {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(htaccess::RewriteOutcome::Redirect(response)) => return response,
|
||||
Ok(htaccess::RewriteOutcome::Forbidden) => return builtin_error_response(StatusCode::FORBIDDEN, &method),
|
||||
Ok(htaccess::RewriteOutcome::Forbidden) => {
|
||||
return builtin_error_response(StatusCode::FORBIDDEN, &method)
|
||||
}
|
||||
Ok(htaccess::RewriteOutcome::Internal { path, query }) => (path, query),
|
||||
Err(_) => return status_response(StatusCode::BAD_REQUEST, "RustProxy: invalid .htaccess rewrite target\n"),
|
||||
Err(_) => {
|
||||
return status_response(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"RustProxy: invalid .htaccess rewrite target\n",
|
||||
)
|
||||
}
|
||||
};
|
||||
if crate::acl::validate_decoded_routing_path(&rewritten_path).is_err()
|
||||
|| hardening_denied_path(service, &rewritten_path)
|
||||
|| path_components(&rewritten_path).ok().is_some_and(|components| components.iter().any(|component| htaccess::protected_component(service.htaccess.as_ref(), component)))
|
||||
|| path_components(&rewritten_path)
|
||||
.ok()
|
||||
.is_some_and(|components| {
|
||||
components.iter().any(|component| {
|
||||
htaccess::protected_component(service.htaccess.as_ref(), component)
|
||||
})
|
||||
})
|
||||
|| htaccess::denied(service.htaccess.as_ref(), &rewritten_path)
|
||||
{
|
||||
return builtin_error_response(StatusCode::FORBIDDEN, &method);
|
||||
@@ -115,7 +138,9 @@ async fn serve_inner(
|
||||
if let Some((script_path, path_info)) = resolve_php_path_info(service, &components).await {
|
||||
let script_components = path_components(&script_path).unwrap_or_default();
|
||||
if hardening_denied_path(service, &script_path)
|
||||
|| script_components.iter().any(|component| htaccess::protected_component(service.htaccess.as_ref(), component))
|
||||
|| script_components.iter().any(|component| {
|
||||
htaccess::protected_component(service.htaccess.as_ref(), component)
|
||||
})
|
||||
|| htaccess::denied(service.htaccess.as_ref(), &script_path)
|
||||
{
|
||||
return builtin_error_response(StatusCode::FORBIDDEN, &method);
|
||||
@@ -138,29 +163,26 @@ async fn serve_inner(
|
||||
}
|
||||
}
|
||||
|
||||
let (effective_path, effective_query) = match resolve_try_files(
|
||||
service,
|
||||
&rewritten_path,
|
||||
rewritten_query.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(TryFilesOutcome::Path { path, query }) => (path, query),
|
||||
Ok(TryFilesOutcome::Status(status)) => return builtin_error_response(status, &method),
|
||||
Err(_) => {
|
||||
return status_response(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"RustProxy: invalid Web Server try_files target\n",
|
||||
)
|
||||
}
|
||||
};
|
||||
let (effective_path, effective_query) =
|
||||
match resolve_try_files(service, &rewritten_path, rewritten_query.as_deref()).await {
|
||||
Ok(TryFilesOutcome::Path { path, query }) => (path, query),
|
||||
Ok(TryFilesOutcome::Status(status)) => return builtin_error_response(status, &method),
|
||||
Err(_) => {
|
||||
return status_response(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"RustProxy: invalid Web Server try_files target\n",
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let components = match path_components(&effective_path) {
|
||||
Ok(components) => components,
|
||||
Err(error) => return io_response(&error),
|
||||
};
|
||||
if hardening_denied_path(service, &effective_path)
|
||||
|| components.iter().any(|component| htaccess::protected_component(service.htaccess.as_ref(), component))
|
||||
|| components
|
||||
.iter()
|
||||
.any(|component| htaccess::protected_component(service.htaccess.as_ref(), component))
|
||||
|| htaccess::denied(service.htaccess.as_ref(), &effective_path)
|
||||
{
|
||||
return builtin_error_response(StatusCode::FORBIDDEN, &method);
|
||||
@@ -169,12 +191,19 @@ async fn serve_inner(
|
||||
let opened = match open_components(service, components.clone()).await {
|
||||
Ok(opened) => opened,
|
||||
Err(error)
|
||||
if matches!(error.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory) =>
|
||||
if matches!(
|
||||
error.kind(),
|
||||
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
|
||||
) =>
|
||||
{
|
||||
if let Some((script_path, path_info)) = resolve_php_path_info(service, &components).await {
|
||||
if let Some((script_path, path_info)) =
|
||||
resolve_php_path_info(service, &components).await
|
||||
{
|
||||
let script_components = path_components(&script_path).unwrap_or_default();
|
||||
if hardening_denied_path(service, &script_path)
|
||||
|| script_components.iter().any(|component| htaccess::protected_component(service.htaccess.as_ref(), component))
|
||||
|| script_components.iter().any(|component| {
|
||||
htaccess::protected_component(service.htaccess.as_ref(), component)
|
||||
})
|
||||
|| htaccess::denied(service.htaccess.as_ref(), &script_path)
|
||||
{
|
||||
return builtin_error_response(StatusCode::FORBIDDEN, &method);
|
||||
@@ -290,15 +319,8 @@ async fn serve_inner(
|
||||
)
|
||||
.await;
|
||||
}
|
||||
return file_response(
|
||||
service,
|
||||
index_opened,
|
||||
&public_path,
|
||||
&method,
|
||||
&headers,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return file_response(service, index_opened, &public_path, &method, &headers, None)
|
||||
.await;
|
||||
}
|
||||
Ok(_) => continue,
|
||||
Err(error)
|
||||
@@ -316,7 +338,11 @@ async fn serve_inner(
|
||||
if method != Method::GET && method != Method::HEAD {
|
||||
return method_not_allowed_response();
|
||||
}
|
||||
if !htaccess::directory_listing(service.htaccess.as_ref(), &components, service.directory_listing) {
|
||||
if !htaccess::directory_listing(
|
||||
service.htaccess.as_ref(),
|
||||
&components,
|
||||
service.directory_listing,
|
||||
) {
|
||||
return builtin_error_response(StatusCode::FORBIDDEN, &method);
|
||||
}
|
||||
let service_for_list = service.clone();
|
||||
@@ -369,6 +395,9 @@ async fn serve_inner(
|
||||
.headers_mut()
|
||||
.insert(CACHE_CONTROL, HeaderValue::from_static("no-store"));
|
||||
response
|
||||
.extensions_mut()
|
||||
.insert(crate::web_server::metrics::ResponseKind::Static);
|
||||
response
|
||||
}
|
||||
|
||||
/// Serve a decoded, validated routing path from a compiled Web Service.
|
||||
@@ -384,11 +413,43 @@ pub async fn serve(
|
||||
body: Body,
|
||||
context: RequestContext,
|
||||
) -> Response {
|
||||
use std::sync::atomic::Ordering;
|
||||
let start = std::time::Instant::now();
|
||||
let _in_flight =
|
||||
crate::web_server::metrics::HeadersInFlight::new(&service.web_metrics.in_flight_headers);
|
||||
let request_method = method.clone();
|
||||
let mut response = serve_inner(service, method, headers, uri, path, body, context).await;
|
||||
add_service_headers(&mut response, service);
|
||||
htaccess::apply_response(service.htaccess.as_ref(), &mut response, path);
|
||||
strip_unnecessary_headers(&mut response, service);
|
||||
service.web_metrics.record(response.status(), start.elapsed().as_micros().min(u64::MAX as u128) as u64);
|
||||
let kind = response
|
||||
.extensions()
|
||||
.get::<crate::web_server::metrics::ResponseKind>()
|
||||
.copied();
|
||||
service.web_metrics.record_web(
|
||||
response.status(),
|
||||
start.elapsed().as_micros().min(u64::MAX as u128) as u64,
|
||||
&request_method,
|
||||
kind,
|
||||
);
|
||||
// Count bytes actually yielded by the Web Server body before downstream compression;
|
||||
// never infer transferred bytes from Content-Length or count an unpolled response.
|
||||
let web = service.web_metrics.clone();
|
||||
let php = if matches!(kind, Some(crate::web_server::metrics::ResponseKind::Php)) {
|
||||
service.php.as_ref().map(|p| p.metrics.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let body = std::mem::replace(response.body_mut(), Body::empty());
|
||||
*response.body_mut() = Body::new(http_body_util::BodyExt::map_frame(body, move |frame| {
|
||||
if let Some(data) = frame.data_ref() {
|
||||
let size = data.len() as u64;
|
||||
web.response_bytes.fetch_add(size, Ordering::Relaxed);
|
||||
if let Some(metrics) = php.as_ref() {
|
||||
metrics.response_bytes.fetch_add(size, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
frame
|
||||
}));
|
||||
response
|
||||
}
|
||||
Reference in new issue
Block a user