v0.25.1
This commit is contained in:
1 parent
87e571c170
commit
fa7abc9e53
308 files changed
+49746
-50237
No files matched your search
@@ -0,0 +1,364 @@
|
||||
use super::{
|
||||
config::{CompiledPhp, FastCgiEndpoint, MAX_HEADER_VALUE_BYTES},
|
||||
filesystem::path_components,
|
||||
htaccess,
|
||||
response::{builtin_error_response, status_response},
|
||||
request::server_protocol,
|
||||
CompiledService, RequestContext,
|
||||
};
|
||||
use anyhow::{Context, ensure, Result};
|
||||
use axum::{body::Body, response::Response};
|
||||
use bytes::Bytes;
|
||||
use futures_util::stream;
|
||||
use http::{
|
||||
header::{ALLOW, CONTENT_LENGTH, CONTENT_TYPE},
|
||||
HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri,
|
||||
};
|
||||
use http_body_util::BodyExt;
|
||||
use std::{collections::BTreeMap, io, path::Path, str::FromStr};
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
|
||||
sync::{mpsc, OwnedSemaphorePermit},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
const FCGI_VERSION_1: u8 = 1;
|
||||
const FCGI_BEGIN_REQUEST: u8 = 1;
|
||||
pub(super) const FCGI_END_REQUEST: u8 = 3;
|
||||
const FCGI_PARAMS: u8 = 4;
|
||||
pub(super) const FCGI_STDIN: u8 = 5;
|
||||
pub(super) const FCGI_STDOUT: u8 = 6;
|
||||
const FCGI_STDERR: u8 = 7;
|
||||
const FCGI_RESPONDER: u16 = 1;
|
||||
const FCGI_REQUEST_ID: u16 = 1;
|
||||
|
||||
pub(super) trait FastCgiIo: AsyncRead + AsyncWrite + Unpin + Send {}
|
||||
impl<T> FastCgiIo for T where T: AsyncRead + AsyncWrite + Unpin + Send {}
|
||||
|
||||
async fn connect_fastcgi(endpoint: &FastCgiEndpoint) -> io::Result<Box<dyn FastCgiIo>> {
|
||||
match endpoint {
|
||||
#[cfg(unix)]
|
||||
FastCgiEndpoint::Unix(path) => {
|
||||
Ok(Box::new(tokio::net::UnixStream::connect(path).await?))
|
||||
}
|
||||
FastCgiEndpoint::Tcp(address) => {
|
||||
Ok(Box::new(tokio::net::TcpStream::connect(address).await?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn write_fcgi_record(
|
||||
io: &mut dyn FastCgiIo,
|
||||
record_type: u8,
|
||||
content: &[u8],
|
||||
) -> io::Result<()> {
|
||||
if content.len() > u16::MAX as usize {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"FastCGI record is too large",
|
||||
));
|
||||
}
|
||||
let padding = (8 - (content.len() % 8)) % 8;
|
||||
let mut header = [0u8; 8];
|
||||
header[0] = FCGI_VERSION_1;
|
||||
header[1] = record_type;
|
||||
header[2..4].copy_from_slice(&FCGI_REQUEST_ID.to_be_bytes());
|
||||
header[4..6].copy_from_slice(&(content.len() as u16).to_be_bytes());
|
||||
header[6] = padding as u8;
|
||||
io.write_all(&header).await?;
|
||||
io.write_all(content).await?;
|
||||
if padding != 0 {
|
||||
io.write_all(&[0u8; 8][..padding]).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode_fcgi_length(target: &mut Vec<u8>, length: usize) -> io::Result<()> {
|
||||
if length < 128 {
|
||||
target.push(length as u8);
|
||||
} else if length <= 0x7fff_ffff {
|
||||
target.extend_from_slice(&((length as u32) | 0x8000_0000).to_be_bytes());
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"FastCGI parameter is too large",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode_fcgi_param(target: &mut Vec<u8>, name: &str, value: &str) -> io::Result<()> {
|
||||
encode_fcgi_length(target, name.len())?;
|
||||
encode_fcgi_length(target, value.len())?;
|
||||
target.extend_from_slice(name.as_bytes());
|
||||
target.extend_from_slice(value.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn fastcgi_header_param(name: &HeaderName) -> Option<String> {
|
||||
let lower = name.as_str();
|
||||
if matches!(
|
||||
lower,
|
||||
"content-type"
|
||||
| "content-length"
|
||||
| "connection"
|
||||
| "transfer-encoding"
|
||||
| "upgrade"
|
||||
| "keep-alive"
|
||||
| "proxy-connection"
|
||||
| "vary"
|
||||
| "te"
|
||||
| "trailer"
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
Some(format!(
|
||||
"HTTP_{}",
|
||||
lower.replace('-', "_").to_ascii_uppercase()
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) fn fastcgi_params(
|
||||
service: &CompiledService,
|
||||
php: &CompiledPhp,
|
||||
method: &Method,
|
||||
headers: &HeaderMap,
|
||||
original_uri: &Uri,
|
||||
script_path: &str,
|
||||
query: Option<&str>,
|
||||
context: &RequestContext,
|
||||
) -> Result<Vec<u8>> {
|
||||
let components = path_components(script_path)?;
|
||||
let mut script_filename = service.root.clone();
|
||||
for component in &components {
|
||||
script_filename.push(component);
|
||||
}
|
||||
let document_root = service.root.to_string_lossy().to_string();
|
||||
let script_filename = script_filename.to_string_lossy().to_string();
|
||||
let request_uri = original_uri
|
||||
.path_and_query()
|
||||
.map(|value| value.as_str())
|
||||
.unwrap_or(original_uri.path());
|
||||
let mut params = BTreeMap::<String, String>::new();
|
||||
params.insert("REQUEST_METHOD".into(), method.as_str().into());
|
||||
params.insert("SCRIPT_FILENAME".into(), script_filename);
|
||||
params.insert("SCRIPT_NAME".into(), script_path.into());
|
||||
params.insert("QUERY_STRING".into(), query.unwrap_or("").into());
|
||||
params.insert("REQUEST_URI".into(), request_uri.into());
|
||||
params.insert("DOCUMENT_ROOT".into(), document_root);
|
||||
params.insert("SERVER_PROTOCOL".into(), server_protocol(context.version).into());
|
||||
params.insert("SERVER_NAME".into(), context.server_name.clone());
|
||||
params.insert("SERVER_PORT".into(), context.server_port.to_string());
|
||||
params.insert("REMOTE_ADDR".into(), context.client_ip.to_string());
|
||||
if let Some(port) = context.client_port {
|
||||
params.insert("REMOTE_PORT".into(), port.to_string());
|
||||
}
|
||||
params.insert("HTTPS".into(), if context.tls { "on" } else { "off" }.into());
|
||||
params.insert("GATEWAY_INTERFACE".into(), "CGI/1.1".into());
|
||||
params.insert("SERVER_SOFTWARE".into(), "RustProxy/0.25.1".into());
|
||||
params.insert("REDIRECT_STATUS".into(), "200".into());
|
||||
if let Some(value) = headers.get(CONTENT_TYPE).and_then(|value| value.to_str().ok()) {
|
||||
params.insert("CONTENT_TYPE".into(), value.into());
|
||||
}
|
||||
if let Some(value) = headers.get(CONTENT_LENGTH).and_then(|value| value.to_str().ok()) {
|
||||
params.insert("CONTENT_LENGTH".into(), value.into());
|
||||
}
|
||||
for (name, value) in headers {
|
||||
// HTTP_PROXY is a well-known CGI/FastCGI injection vector (httpoxy).
|
||||
// This must never depend on the optional Web Service hardening switch.
|
||||
if name.as_str() == "proxy"
|
||||
|| (!php.pass_authorization && name.as_str() == "authorization")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(param) = fastcgi_header_param(name) else { continue };
|
||||
let Ok(value) = value.to_str() else { continue };
|
||||
if value.len() > MAX_HEADER_VALUE_BYTES {
|
||||
continue;
|
||||
}
|
||||
params
|
||||
.entry(param)
|
||||
.and_modify(|existing| {
|
||||
if existing.len() + value.len() + 2 <= MAX_HEADER_VALUE_BYTES {
|
||||
existing.push_str(", ");
|
||||
existing.push_str(value);
|
||||
}
|
||||
})
|
||||
.or_insert_with(|| value.to_owned());
|
||||
}
|
||||
// Trusted operator-defined parameters cannot override request-derived/core CGI names.
|
||||
for (name, value) in &php.custom_params {
|
||||
params.insert(name.clone(), value.clone());
|
||||
}
|
||||
if !php.php_values.is_empty() {
|
||||
let mut value = String::new();
|
||||
for (name, item) in &php.php_values {
|
||||
if !value.is_empty() {
|
||||
value.push('\n');
|
||||
}
|
||||
value.push_str(name);
|
||||
value.push('=');
|
||||
value.push_str(item);
|
||||
}
|
||||
params.insert("PHP_VALUE".into(), value);
|
||||
}
|
||||
// Request headers cannot synthesize PHP_VALUE/PHP_ADMIN_VALUE because only HTTP_*
|
||||
// names are derived above. PHP_VALUE comes exclusively from validated operator config.
|
||||
let mut encoded = Vec::with_capacity(4096);
|
||||
for (name, value) in params {
|
||||
encode_fcgi_param(&mut encoded, &name, &value)?;
|
||||
ensure!(
|
||||
encoded.len() <= 512 * 1024,
|
||||
"FastCGI parameter block exceeds 512 KiB"
|
||||
);
|
||||
}
|
||||
Ok(encoded)
|
||||
}
|
||||
|
||||
pub(super) async fn read_fcgi_record(
|
||||
io: &mut dyn FastCgiIo,
|
||||
deadline: Instant,
|
||||
) -> io::Result<(u8, Vec<u8>)> {
|
||||
let mut header = [0u8; 8];
|
||||
tokio::time::timeout_at(deadline, io.read_exact(&mut header))
|
||||
.await
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "FastCGI response timeout"))??;
|
||||
if header[0] != FCGI_VERSION_1 || u16::from_be_bytes([header[2], header[3]]) != FCGI_REQUEST_ID {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"invalid FastCGI record header",
|
||||
));
|
||||
}
|
||||
let length = u16::from_be_bytes([header[4], header[5]]) as usize;
|
||||
let padding = header[6] as usize;
|
||||
let mut content = vec![0u8; length];
|
||||
if length != 0 {
|
||||
tokio::time::timeout_at(deadline, io.read_exact(&mut content))
|
||||
.await
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "FastCGI response timeout"))??;
|
||||
}
|
||||
if padding != 0 {
|
||||
let mut discard = [0u8; 255];
|
||||
tokio::time::timeout_at(deadline, io.read_exact(&mut discard[..padding]))
|
||||
.await
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "FastCGI response timeout"))??;
|
||||
}
|
||||
Ok((header[1], content))
|
||||
}
|
||||
|
||||
fn find_cgi_header_end(value: &[u8]) -> Option<(usize, usize)> {
|
||||
value
|
||||
.windows(4)
|
||||
.position(|window| window == b"\r\n\r\n")
|
||||
.map(|position| (position, 4))
|
||||
.or_else(|| {
|
||||
value
|
||||
.windows(2)
|
||||
.position(|window| window == b"\n\n")
|
||||
.map(|position| (position, 2))
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn parse_cgi_headers(value: &[u8]) -> Result<(StatusCode, HeaderMap)> {
|
||||
let text = std::str::from_utf8(value).context("PHP-FPM returned non-UTF-8 CGI headers")?;
|
||||
let mut status = StatusCode::OK;
|
||||
let mut headers = HeaderMap::new();
|
||||
for (index, line) in text.lines().enumerate() {
|
||||
ensure!(index < 128, "PHP-FPM returned too many CGI headers");
|
||||
let line = line.trim_end_matches('\r');
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let (name, value) = line
|
||||
.split_once(':')
|
||||
.context("PHP-FPM returned a malformed CGI header")?;
|
||||
if name.eq_ignore_ascii_case("status") {
|
||||
let code = value
|
||||
.trim()
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.context("PHP-FPM returned an empty Status header")?
|
||||
.parse::<u16>()?;
|
||||
ensure!(
|
||||
(200..=599).contains(&code),
|
||||
"PHP-FPM returned an unsupported informational/invalid status"
|
||||
);
|
||||
status = StatusCode::from_u16(code)?;
|
||||
continue;
|
||||
}
|
||||
let name = HeaderName::from_str(name.trim())?;
|
||||
if matches!(
|
||||
name.as_str(),
|
||||
"connection"
|
||||
| "content-length"
|
||||
| "transfer-encoding"
|
||||
| "upgrade"
|
||||
| "keep-alive"
|
||||
| "proxy-connection"
|
||||
| "te"
|
||||
| "trailer"
|
||||
| "proxy-authenticate"
|
||||
| "proxy-authorization"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let value = HeaderValue::from_str(value.trim())?;
|
||||
headers.append(name, value);
|
||||
}
|
||||
Ok((status, headers))
|
||||
}
|
||||
|
||||
async fn drain_fastcgi_body(
|
||||
mut io: Box<dyn FastCgiIo>,
|
||||
deadline: Instant,
|
||||
tx: mpsc::Sender<io::Result<Bytes>>,
|
||||
initial: Vec<u8>,
|
||||
send_body: bool,
|
||||
stderr_log_bytes: usize,
|
||||
_permit: OwnedSemaphorePermit,
|
||||
) {
|
||||
if send_body && !initial.is_empty() && tx.send(Ok(Bytes::from(initial))).await.is_err() {
|
||||
return;
|
||||
}
|
||||
let mut stderr = Vec::new();
|
||||
loop {
|
||||
match read_fcgi_record(io.as_mut(), deadline).await {
|
||||
Ok((FCGI_STDOUT, content)) => {
|
||||
if send_body && !content.is_empty() && tx.send(Ok(Bytes::from(content))).await.is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Ok((FCGI_STDERR, content)) => {
|
||||
let room = stderr_log_bytes.saturating_sub(stderr.len());
|
||||
stderr.extend_from_slice(&content[..content.len().min(room)]);
|
||||
}
|
||||
Ok((FCGI_END_REQUEST, _)) => break,
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
if send_body {
|
||||
let _ = tx.send(Err(error)).await;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !stderr.is_empty() {
|
||||
tracing::warn!(
|
||||
stderr = %String::from_utf8_lossy(&stderr),
|
||||
"PHP-FPM FastCGI stderr"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum FastCgiRequestWriteError {
|
||||
Io(io::Error),
|
||||
BodyTooLarge,
|
||||
}
|
||||
|
||||
impl From<io::Error> for FastCgiRequestWriteError {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::Io(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
pub(super) async fn php_response(
|
||||
service: &CompiledService,
|
||||
php: &CompiledPhp,
|
||||
method: Method,
|
||||
headers: HeaderMap,
|
||||
original_uri: Uri,
|
||||
script_path: String,
|
||||
query: Option<String>,
|
||||
body: Body,
|
||||
context: RequestContext,
|
||||
) -> Response {
|
||||
if php.max_request_body_bytes > 0 {
|
||||
if let Some(length) = headers
|
||||
.get(CONTENT_LENGTH)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.parse::<u64>().ok())
|
||||
{
|
||||
if length > php.max_request_body_bytes {
|
||||
return builtin_error_response(StatusCode::PAYLOAD_TOO_LARGE, &method);
|
||||
}
|
||||
}
|
||||
}
|
||||
let permit = match php.permits.clone().try_acquire_owned() {
|
||||
Ok(permit) => permit,
|
||||
Err(_) => {
|
||||
let mut response = builtin_error_response(StatusCode::SERVICE_UNAVAILABLE, &method);
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("retry-after", HeaderValue::from_static("1"));
|
||||
return response;
|
||||
}
|
||||
};
|
||||
let mut io = match tokio::time::timeout(php.connect_timeout, connect_fastcgi(&php.endpoint)).await {
|
||||
Ok(Ok(io)) => io,
|
||||
Ok(Err(error)) => {
|
||||
tracing::warn!(%error, service = %service.id, "PHP-FPM connection failed");
|
||||
return builtin_error_response(StatusCode::BAD_GATEWAY, &method);
|
||||
}
|
||||
Err(_) => return builtin_error_response(StatusCode::GATEWAY_TIMEOUT, &method),
|
||||
};
|
||||
let params = match fastcgi_params(
|
||||
service,
|
||||
php,
|
||||
&method,
|
||||
&headers,
|
||||
&original_uri,
|
||||
&script_path,
|
||||
query.as_deref(),
|
||||
&context,
|
||||
) {
|
||||
Ok(params) => params,
|
||||
Err(error) => {
|
||||
tracing::warn!(%error, service = %service.id, "PHP-FPM parameter preparation failed");
|
||||
return builtin_error_response(StatusCode::BAD_GATEWAY, &method);
|
||||
}
|
||||
};
|
||||
let deadline = Instant::now() + php.request_timeout;
|
||||
let exchange = async {
|
||||
let mut begin = [0u8; 8];
|
||||
begin[..2].copy_from_slice(&FCGI_RESPONDER.to_be_bytes());
|
||||
write_fcgi_record(io.as_mut(), FCGI_BEGIN_REQUEST, &begin).await?;
|
||||
for chunk in params.chunks(u16::MAX as usize) {
|
||||
write_fcgi_record(io.as_mut(), FCGI_PARAMS, chunk).await?;
|
||||
}
|
||||
write_fcgi_record(io.as_mut(), FCGI_PARAMS, &[]).await?;
|
||||
|
||||
let mut body = body;
|
||||
let mut body_bytes = 0u64;
|
||||
while let Some(frame) = body.frame().await {
|
||||
let frame = frame
|
||||
.map_err(|error| FastCgiRequestWriteError::Io(io::Error::other(error.to_string())))?;
|
||||
if let Ok(data) = frame.into_data() {
|
||||
body_bytes = body_bytes.saturating_add(data.len() as u64);
|
||||
if php.max_request_body_bytes > 0 && body_bytes > php.max_request_body_bytes {
|
||||
return Err(FastCgiRequestWriteError::BodyTooLarge);
|
||||
}
|
||||
for chunk in data.chunks(u16::MAX as usize) {
|
||||
write_fcgi_record(io.as_mut(), FCGI_STDIN, chunk).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
write_fcgi_record(io.as_mut(), FCGI_STDIN, &[]).await?;
|
||||
io.flush().await?;
|
||||
Ok::<(), FastCgiRequestWriteError>(())
|
||||
};
|
||||
match tokio::time::timeout_at(deadline, exchange).await {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(FastCgiRequestWriteError::BodyTooLarge)) => {
|
||||
return builtin_error_response(StatusCode::PAYLOAD_TOO_LARGE, &method);
|
||||
}
|
||||
Ok(Err(FastCgiRequestWriteError::Io(error))) => {
|
||||
tracing::warn!(%error, service = %service.id, "PHP-FPM request write failed");
|
||||
return builtin_error_response(StatusCode::BAD_GATEWAY, &method);
|
||||
}
|
||||
Err(_) => return builtin_error_response(StatusCode::GATEWAY_TIMEOUT, &method),
|
||||
}
|
||||
|
||||
let mut stdout = Vec::new();
|
||||
let mut stderr = Vec::new();
|
||||
let (status, response_headers, initial_body) = loop {
|
||||
let record = read_fcgi_record(io.as_mut(), deadline).await;
|
||||
let (record_type, content) = match record {
|
||||
Ok(value) => value,
|
||||
Err(error) if error.kind() == io::ErrorKind::TimedOut => {
|
||||
return builtin_error_response(StatusCode::GATEWAY_TIMEOUT, &method)
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!(%error, service = %service.id, "PHP-FPM response read failed");
|
||||
return builtin_error_response(StatusCode::BAD_GATEWAY, &method);
|
||||
}
|
||||
};
|
||||
match record_type {
|
||||
FCGI_STDOUT => {
|
||||
stdout.extend_from_slice(&content);
|
||||
if stdout.len() > php.max_response_header_bytes {
|
||||
return builtin_error_response(StatusCode::BAD_GATEWAY, &method);
|
||||
}
|
||||
if let Some((position, separator_len)) = find_cgi_header_end(&stdout) {
|
||||
let body = stdout[position + separator_len..].to_vec();
|
||||
let parsed = parse_cgi_headers(&stdout[..position]);
|
||||
match parsed {
|
||||
Ok((status, headers)) => break (status, headers, body),
|
||||
Err(error) => {
|
||||
tracing::warn!(%error, service = %service.id, "PHP-FPM CGI headers rejected");
|
||||
return builtin_error_response(StatusCode::BAD_GATEWAY, &method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FCGI_STDERR => {
|
||||
let room = php.stderr_log_bytes.saturating_sub(stderr.len());
|
||||
stderr.extend_from_slice(&content[..content.len().min(room)]);
|
||||
}
|
||||
FCGI_END_REQUEST => {
|
||||
if !stderr.is_empty() {
|
||||
tracing::warn!(stderr = %String::from_utf8_lossy(&stderr), service = %service.id, "PHP-FPM FastCGI stderr");
|
||||
}
|
||||
return builtin_error_response(StatusCode::BAD_GATEWAY, &method);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
if !stderr.is_empty() {
|
||||
tracing::warn!(stderr = %String::from_utf8_lossy(&stderr), service = %service.id, "PHP-FPM FastCGI stderr");
|
||||
}
|
||||
let send_body = method != Method::HEAD
|
||||
&& status != StatusCode::NO_CONTENT
|
||||
&& status != StatusCode::NOT_MODIFIED
|
||||
&& !status.is_informational();
|
||||
let (tx, rx) = mpsc::channel::<io::Result<Bytes>>(8);
|
||||
tokio::spawn(drain_fastcgi_body(
|
||||
io,
|
||||
deadline,
|
||||
tx,
|
||||
initial_body,
|
||||
send_body,
|
||||
php.stderr_log_bytes,
|
||||
permit,
|
||||
));
|
||||
let mut response = Response::new(if send_body {
|
||||
Body::from_stream(stream::unfold(rx, |mut rx| async move {
|
||||
rx.recv().await.map(|item| (item, rx))
|
||||
}))
|
||||
} else {
|
||||
Body::empty()
|
||||
});
|
||||
*response.status_mut() = status;
|
||||
*response.headers_mut() = response_headers;
|
||||
response
|
||||
}
|
||||
|
||||
pub(super) fn method_not_allowed_response() -> Response {
|
||||
let mut response = status_response(
|
||||
StatusCode::METHOD_NOT_ALLOWED,
|
||||
"RustProxy: Web Server method is not allowed for a static resource\n",
|
||||
);
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(ALLOW, HeaderValue::from_static("GET, HEAD"));
|
||||
response
|
||||
}
|
||||
|
||||
pub(super) fn php_for_path<'a>(service: &'a CompiledService, path: &Path) -> Option<&'a CompiledPhp> {
|
||||
let php = service.php.as_ref()?;
|
||||
let extension = path
|
||||
.extension()
|
||||
.and_then(|value| value.to_str())?
|
||||
.to_ascii_lowercase();
|
||||
(php.extensions.contains(&extension) || htaccess::php_extension(service.htaccess.as_ref(), path)).then_some(php)
|
||||
}
|
||||
|
||||
pub(super) fn protected_script_without_handler(service: &CompiledService, path: &Path) -> bool {
|
||||
if service.php.is_some() { return false; }
|
||||
path.extension()
|
||||
.and_then(|value| value.to_str())
|
||||
.map(|value| value.to_ascii_lowercase())
|
||||
.is_some_and(|extension| service.script_extensions.contains(&extension))
|
||||
}
|
||||
Reference in new issue
Block a user