v0.29.4
This commit is contained in:
1 parent
f87eec7287
commit
dd8c195600
38 files changed
+5491
-4682
No files matched your search
@@ -1,3 +1,72 @@
|
||||
impl Php {
|
||||
pub fn validate(&mut self) -> Result<()> {
|
||||
ensure!(
|
||||
self.extensions.len() <= MAX_PHP_EXTENSIONS,
|
||||
"Web Service PHP allows at most 16 extensions"
|
||||
);
|
||||
self.extensions = normalize_extensions(
|
||||
std::mem::take(&mut self.extensions),
|
||||
"Web Service PHP extension",
|
||||
)?;
|
||||
self.endpoint = self.endpoint.trim().to_owned();
|
||||
ensure!(
|
||||
self.connect_timeout_ms >= 50 && self.connect_timeout_ms <= 60_000,
|
||||
"Web Service PHP connect timeout must be 50..60000 ms"
|
||||
);
|
||||
ensure!(
|
||||
self.request_timeout_ms >= 100 && self.request_timeout_ms <= 3_600_000,
|
||||
"Web Service PHP request timeout must be 100..3600000 ms"
|
||||
);
|
||||
ensure!(
|
||||
(1..=4096).contains(&self.max_concurrent_requests),
|
||||
"Web Service PHP max concurrent requests must be 1..4096"
|
||||
);
|
||||
ensure!(
|
||||
self.max_request_body_bytes <= 8_u64 * 1024 * 1024 * 1024,
|
||||
"Web Service PHP request body limit must be 0..8589934592 bytes"
|
||||
);
|
||||
ensure!(
|
||||
self.max_response_header_bytes >= 4096
|
||||
&& self.max_response_header_bytes <= 1024 * 1024,
|
||||
"Web Service PHP response header limit must be 4096..1048576 bytes"
|
||||
);
|
||||
ensure!(
|
||||
self.stderr_log_bytes <= 64 * 1024,
|
||||
"Web Service PHP stderr log limit must be 0..65536 bytes"
|
||||
);
|
||||
self.custom_params = normalize_fastcgi_params(std::mem::take(&mut self.custom_params))?;
|
||||
self.php_values = normalize_php_values(std::mem::take(&mut self.php_values))?;
|
||||
self.php_admin_values = normalize_php_values(std::mem::take(&mut self.php_admin_values))?;
|
||||
ensure!(!self.php_values.keys().any(|key| self.php_admin_values.contains_key(key)), "PHP_VALUE and PHP_ADMIN_VALUE must not define the same directive");
|
||||
let php_custom_bytes = self
|
||||
.custom_params
|
||||
.iter()
|
||||
.chain(self.php_values.iter())
|
||||
.chain(self.php_admin_values.iter())
|
||||
.map(|(name, value)| name.len() + value.len() + 2)
|
||||
.sum::<usize>();
|
||||
ensure!(
|
||||
php_custom_bytes <= 128 * 1024,
|
||||
"Web Service PHP custom FastCGI/PHP_VALUE data must be at most 128 KiB"
|
||||
);
|
||||
if self.enabled {
|
||||
ensure!(!self.extensions.is_empty(), "PHP-FPM needs executable extensions");
|
||||
FastCgiEndpoint::parse(&self.endpoint)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PhpFpmProfile {
|
||||
pub fn validate(&mut self) -> Result<()> {
|
||||
ensure!(crate::config::valid_id(&self.id), "invalid PHP-FPM profile id");
|
||||
self.name = self.name.trim().to_owned();
|
||||
ensure!(!self.name.is_empty() && self.name.len() <= MAX_SERVICE_NAME_BYTES
|
||||
&& !self.name.chars().any(char::is_control), "PHP-FPM profile name is invalid");
|
||||
self.php.validate()
|
||||
}
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub fn validate(&mut self) -> Result<()> {
|
||||
ensure!(crate::config::valid_id(&self.id), "invalid Web Service id");
|
||||
@@ -206,56 +275,10 @@ impl Service {
|
||||
"Web Service download extension",
|
||||
)?;
|
||||
|
||||
ensure!(
|
||||
self.php.extensions.len() <= MAX_PHP_EXTENSIONS,
|
||||
"Web Service PHP allows at most 16 extensions"
|
||||
);
|
||||
self.php.extensions = normalize_extensions(
|
||||
std::mem::take(&mut self.php.extensions),
|
||||
"Web Service PHP extension",
|
||||
)?;
|
||||
self.php.endpoint = self.php.endpoint.trim().to_owned();
|
||||
ensure!(
|
||||
self.php.connect_timeout_ms >= 50 && self.php.connect_timeout_ms <= 60_000,
|
||||
"Web Service PHP connect timeout must be 50..60000 ms"
|
||||
);
|
||||
ensure!(
|
||||
self.php.request_timeout_ms >= 100 && self.php.request_timeout_ms <= 3_600_000,
|
||||
"Web Service PHP request timeout must be 100..3600000 ms"
|
||||
);
|
||||
ensure!(
|
||||
(1..=4096).contains(&self.php.max_concurrent_requests),
|
||||
"Web Service PHP max concurrent requests must be 1..4096"
|
||||
);
|
||||
ensure!(
|
||||
self.php.max_request_body_bytes <= 8_u64 * 1024 * 1024 * 1024,
|
||||
"Web Service PHP request body limit must be 0..8589934592 bytes"
|
||||
);
|
||||
ensure!(
|
||||
self.php.max_response_header_bytes >= 4096
|
||||
&& self.php.max_response_header_bytes <= 1024 * 1024,
|
||||
"Web Service PHP response header limit must be 4096..1048576 bytes"
|
||||
);
|
||||
ensure!(
|
||||
self.php.stderr_log_bytes <= 64 * 1024,
|
||||
"Web Service PHP stderr log limit must be 0..65536 bytes"
|
||||
);
|
||||
self.php.custom_params = normalize_fastcgi_params(std::mem::take(&mut self.php.custom_params))?;
|
||||
self.php.php_values = normalize_php_values(std::mem::take(&mut self.php.php_values))?;
|
||||
self.php.php_admin_values = normalize_php_values(std::mem::take(&mut self.php.php_admin_values))?;
|
||||
ensure!(!self.php.php_values.keys().any(|key| self.php.php_admin_values.contains_key(key)), "PHP_VALUE and PHP_ADMIN_VALUE must not define the same directive");
|
||||
let php_custom_bytes = self
|
||||
.php
|
||||
.custom_params
|
||||
.iter()
|
||||
.chain(self.php.php_values.iter())
|
||||
.chain(self.php.php_admin_values.iter())
|
||||
.map(|(name, value)| name.len() + value.len() + 2)
|
||||
.sum::<usize>();
|
||||
ensure!(
|
||||
php_custom_bytes <= 128 * 1024,
|
||||
"Web Service PHP custom FastCGI/PHP_VALUE data must be at most 128 KiB"
|
||||
);
|
||||
self.php.validate()?;
|
||||
if let Some(id) = &self.php_profile_id {
|
||||
ensure!(crate::config::valid_id(id), "invalid PHP-FPM profile reference");
|
||||
}
|
||||
htaccess::validate(&mut self.htaccess)?;
|
||||
|
||||
if self.php.enabled {
|
||||
|
||||
@@ -309,6 +309,16 @@ impl Default for Php {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reusable PHP-FPM configuration. A Web Service can reference one profile or
|
||||
/// keep its local `php` settings. Profiles are part of the revisioned Config.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct PhpFpmProfile {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub php: Php,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Hardening {
|
||||
@@ -352,6 +362,10 @@ pub struct Service {
|
||||
pub download_extensions: Vec<String>,
|
||||
/// Symlink handling. The conservative default is `deny`.
|
||||
pub symlink_policy: SymlinkPolicy,
|
||||
/// Optional reference to a shared FPM profile; `php` stays the standalone
|
||||
/// configuration and is preserved when attaching/detaching a profile.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub php_profile_id: Option<String>,
|
||||
/// Optional FastCGI/PHP-FPM execution for configured script extensions.
|
||||
pub php: Php,
|
||||
/// Optional bounded Apache .htaccess compatibility layer compiled at Web Service apply time.
|
||||
@@ -379,6 +393,7 @@ impl Default for Service {
|
||||
compression: crate::compression::Overrides::default(),
|
||||
download_extensions: Vec::new(),
|
||||
symlink_policy: SymlinkPolicy::Deny,
|
||||
php_profile_id: None,
|
||||
php: Php::default(),
|
||||
htaccess: HtaccessConfig::default(),
|
||||
hardening: Hardening::default(),
|
||||
|
||||
@@ -201,6 +201,9 @@ pub(super) fn fastcgi_params(
|
||||
})
|
||||
.or_insert_with(|| value.to_owned());
|
||||
}
|
||||
// HTTP/2 and HTTP/3 use :authority and may not carry a literal Host
|
||||
// header. WordPress and other CGI applications require HTTP_HOST.
|
||||
params.entry("HTTP_HOST".into()).or_insert_with(|| context.server_name.clone());
|
||||
// Trusted operator-defined parameters cannot override request-derived/core CGI names.
|
||||
for (name, value) in &php.custom_params {
|
||||
params.insert(name.clone(), value.clone());
|
||||
|
||||
@@ -16,7 +16,7 @@ mod security;
|
||||
mod serve;
|
||||
|
||||
pub use config::{
|
||||
CacheRule, CompiledService, Hardening, Php, Precompressed, RewriteRule, Service, SymlinkPolicy,
|
||||
CacheRule, CompiledService, Hardening, Php, PhpFpmProfile, Precompressed, RewriteRule, Service, SymlinkPolicy,
|
||||
};
|
||||
pub use htaccess::Config as HtaccessConfig;
|
||||
pub use request::RequestContext;
|
||||
|
||||
@@ -285,3 +285,77 @@ fn php_admin_values_are_operator_only_and_serialized() {
|
||||
configured.php.php_admin_values.insert("unsafe".into(), "value\nother=1".into());
|
||||
assert!(configured.validate().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fastcgi_always_sets_http_host_for_http2_http3_wordpress_requests() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let mut configured = Service {
|
||||
id: "host-fallback".into(),
|
||||
name: "Host fallback".into(),
|
||||
document_root: tmp.path().to_path_buf(),
|
||||
php: Php { enabled: true, endpoint: "tcp:127.0.0.1:9000".into(), ..Default::default() },
|
||||
..Default::default()
|
||||
};
|
||||
configured.validate().unwrap();
|
||||
let service = configured.compile().unwrap();
|
||||
let php = service.php.as_ref().unwrap();
|
||||
let uri: Uri = "/wp-admin/index.php".parse().unwrap();
|
||||
for version in [Version::HTTP_2, Version::HTTP_3] {
|
||||
let encoded = fastcgi_params(
|
||||
&service,
|
||||
php,
|
||||
&Method::GET,
|
||||
&HeaderMap::new(),
|
||||
&uri,
|
||||
"/wp-admin/index.php",
|
||||
None,
|
||||
None,
|
||||
&RequestContext {
|
||||
client_ip: "127.0.0.1".parse().unwrap(),
|
||||
client_port: Some(12345),
|
||||
server_name: "example.test".into(),
|
||||
server_port: 443,
|
||||
tls: true,
|
||||
version,
|
||||
},
|
||||
).unwrap();
|
||||
assert!(encoded.windows(b"HTTP_HOST".len()).any(|w| w == b"HTTP_HOST"));
|
||||
assert!(encoded.windows(b"example.test".len()).any(|w| w == b"example.test"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fastcgi_http_host_keeps_explicit_host_and_port() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let mut configured = Service {
|
||||
id: "host-port".into(),
|
||||
name: "Host port".into(),
|
||||
document_root: tmp.path().to_path_buf(),
|
||||
php: Php { enabled: true, endpoint: "tcp:127.0.0.1:9000".into(), ..Default::default() },
|
||||
..Default::default()
|
||||
};
|
||||
configured.validate().unwrap();
|
||||
let service = configured.compile().unwrap();
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(http::header::HOST, HeaderValue::from_static("example.test:8443"));
|
||||
let encoded = fastcgi_params(
|
||||
&service,
|
||||
service.php.as_ref().unwrap(),
|
||||
&Method::GET,
|
||||
&headers,
|
||||
&"/index.php".parse().unwrap(),
|
||||
"/index.php",
|
||||
None,
|
||||
None,
|
||||
&RequestContext {
|
||||
client_ip: "127.0.0.1".parse().unwrap(),
|
||||
client_port: None,
|
||||
server_name: "example.test".into(),
|
||||
server_port: 443,
|
||||
tls: true,
|
||||
version: Version::HTTP_11,
|
||||
},
|
||||
).unwrap();
|
||||
assert!(encoded.windows(b"HTTP_HOST".len()).any(|w| w == b"HTTP_HOST"));
|
||||
assert!(encoded.windows(b"example.test:8443".len()).any(|w| w == b"example.test:8443"));
|
||||
}
|
||||
Reference in new issue
Block a user