v0.25.5
This commit is contained in:
1 parent
1dea7f55c8
commit
c819f0a335
151 files changed
+7610
-4264
No files matched your search
Vendored
+76
-44
@@ -92,57 +92,89 @@ fn canonical_url(scheme: &str, authority: &str, target: &str) -> String {
|
||||
|
||||
fn normalized_matcher(request: &InvalidationRequest) -> Result<SelectorMatcher> {
|
||||
ensure!(!request.value.is_empty(), "invalidation value is required");
|
||||
ensure!(request.value.len() <= 8192, "invalidation value is too long");
|
||||
ensure!(
|
||||
request.value.len() <= 8192,
|
||||
"invalidation value is too long"
|
||||
);
|
||||
if let Some(vhost) = &request.vhost {
|
||||
ensure!(!vhost.is_empty() && vhost.len() <= 64, "invalid vhost selector");
|
||||
ensure!(
|
||||
!vhost.is_empty() && vhost.len() <= 64,
|
||||
"invalid vhost selector"
|
||||
);
|
||||
}
|
||||
if let Some(host) = &request.host {
|
||||
ensure!(!host.is_empty() && host.len() <= 253, "invalid host selector");
|
||||
ensure!(
|
||||
!host.is_empty() && host.len() <= 253,
|
||||
"invalid host selector"
|
||||
);
|
||||
}
|
||||
let mut value = request.value.trim().to_owned();
|
||||
let mut scheme = None;
|
||||
let mut host = request.host.as_ref().map(|value| value.to_ascii_lowercase());
|
||||
let regex = match request.selector {
|
||||
InvalidationSelector::Exact | InvalidationSelector::Prefix => {
|
||||
if value.starts_with("http://") || value.starts_with("https://") {
|
||||
let url = url::Url::parse(&value).context("invalid purge URL")?;
|
||||
ensure!(url.username().is_empty() && url.password().is_none(), "purge URL must not contain credentials");
|
||||
ensure!(url.fragment().is_none(), "purge URL must not contain a fragment");
|
||||
scheme = Some(url.scheme().to_ascii_lowercase());
|
||||
let authority = match url.port() {
|
||||
Some(port) => format!("{}:{port}", url.host_str().context("purge URL host missing")?),
|
||||
None => url.host_str().context("purge URL host missing")?.to_owned(),
|
||||
};
|
||||
host = Some(authority.to_ascii_lowercase());
|
||||
value = url.path().to_owned();
|
||||
if let Some(query) = url.query() {
|
||||
value.push('?');
|
||||
value.push_str(query);
|
||||
let mut host = request
|
||||
.host
|
||||
.as_ref()
|
||||
.map(|value| value.to_ascii_lowercase());
|
||||
let regex =
|
||||
match request.selector {
|
||||
InvalidationSelector::Exact | InvalidationSelector::Prefix => {
|
||||
if value.starts_with("http://") || value.starts_with("https://") {
|
||||
let url = url::Url::parse(&value).context("invalid purge URL")?;
|
||||
ensure!(
|
||||
url.username().is_empty() && url.password().is_none(),
|
||||
"purge URL must not contain credentials"
|
||||
);
|
||||
ensure!(
|
||||
url.fragment().is_none(),
|
||||
"purge URL must not contain a fragment"
|
||||
);
|
||||
scheme = Some(url.scheme().to_ascii_lowercase());
|
||||
let authority = match url.port() {
|
||||
Some(port) => format!(
|
||||
"{}:{port}",
|
||||
url.host_str().context("purge URL host missing")?
|
||||
),
|
||||
None => url.host_str().context("purge URL host missing")?.to_owned(),
|
||||
};
|
||||
host = Some(authority.to_ascii_lowercase());
|
||||
value = url.path().to_owned();
|
||||
if let Some(query) = url.query() {
|
||||
value.push('?');
|
||||
value.push_str(query);
|
||||
}
|
||||
}
|
||||
ensure!(
|
||||
value.starts_with('/'),
|
||||
"exact/prefix invalidation must use an absolute URL or /path target"
|
||||
);
|
||||
None
|
||||
}
|
||||
ensure!(value.starts_with('/'), "exact/prefix invalidation must use an absolute URL or /path target");
|
||||
None
|
||||
}
|
||||
InvalidationSelector::Tag => {
|
||||
ensure!(value.len() <= 128, "cache tag is too long");
|
||||
ensure!(value.bytes().all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-' | b'.' | b':' | b'/')), "invalid cache tag");
|
||||
None
|
||||
}
|
||||
InvalidationSelector::Regex => {
|
||||
ensure!(value.len() <= 512, "regex is too long");
|
||||
Some(
|
||||
regex::RegexBuilder::new(&value)
|
||||
.size_limit(1 << 20)
|
||||
.build()
|
||||
.context("invalid or too expensive regex")?,
|
||||
)
|
||||
}
|
||||
InvalidationSelector::CacheKey => {
|
||||
ensure!(parse_key_hex(&value).is_some(), "cache_key must be 64 hexadecimal characters");
|
||||
value.make_ascii_lowercase();
|
||||
None
|
||||
}
|
||||
};
|
||||
InvalidationSelector::Tag => {
|
||||
ensure!(value.len() <= 128, "cache tag is too long");
|
||||
ensure!(
|
||||
value.bytes().all(|b| b.is_ascii_alphanumeric()
|
||||
|| matches!(b, b'_' | b'-' | b'.' | b':' | b'/')),
|
||||
"invalid cache tag"
|
||||
);
|
||||
None
|
||||
}
|
||||
InvalidationSelector::Regex => {
|
||||
ensure!(value.len() <= 512, "regex is too long");
|
||||
Some(
|
||||
regex::RegexBuilder::new(&value)
|
||||
.size_limit(1 << 20)
|
||||
.build()
|
||||
.context("invalid or too expensive regex")?,
|
||||
)
|
||||
}
|
||||
InvalidationSelector::CacheKey => {
|
||||
ensure!(
|
||||
parse_key_hex(&value).is_some(),
|
||||
"cache_key must be 64 hexadecimal characters"
|
||||
);
|
||||
value.make_ascii_lowercase();
|
||||
None
|
||||
}
|
||||
};
|
||||
Ok(SelectorMatcher {
|
||||
selector: request.selector,
|
||||
value,
|
||||
@@ -198,7 +230,7 @@ fn matcher_matches(
|
||||
InvalidationSelector::Tag => {
|
||||
host_matches(matcher, scheme, authority, domain)
|
||||
&& tags.iter().any(|tag| tag == &matcher.value)
|
||||
},
|
||||
}
|
||||
InvalidationSelector::Regex => {
|
||||
if !host_matches(matcher, scheme, authority, domain) {
|
||||
return false;
|
||||
|
||||
Vendored
+13
-6
@@ -56,24 +56,31 @@ impl Cache {
|
||||
let started = Instant::now();
|
||||
let matcher = normalized_matcher(&request)?;
|
||||
if requires_wide_confirmation(&matcher) {
|
||||
ensure!(request.confirm_wide, "wide invalidation requires confirm_wide=true");
|
||||
ensure!(
|
||||
request.confirm_wide,
|
||||
"wide invalidation requires confirm_wide=true"
|
||||
);
|
||||
}
|
||||
match request.operation {
|
||||
InvalidationOperation::Ban => {
|
||||
let ttl = request.ttl_seconds.unwrap_or(DEFAULT_BAN_TTL_SECONDS);
|
||||
ensure!((1..=MAX_BAN_TTL_SECONDS).contains(&ttl), "BAN ttl_seconds must be 1..604800");
|
||||
ensure!(
|
||||
(1..=MAX_BAN_TTL_SECONDS).contains(&ttl),
|
||||
"BAN ttl_seconds must be 1..604800"
|
||||
);
|
||||
let now = SystemTime::now();
|
||||
let mut bans = self.bans.lock().unwrap_or_else(|e| e.into_inner());
|
||||
bans.retain(|rule| !rule.expired(now));
|
||||
ensure!(bans.len() < MAX_BAN_RULES, "active BAN rule limit reached");
|
||||
let id = self.ban_sequence.fetch_add(1, Ordering::Relaxed).wrapping_add(1);
|
||||
let id = self
|
||||
.ban_sequence
|
||||
.fetch_add(1, Ordering::Relaxed)
|
||||
.wrapping_add(1);
|
||||
bans.push(BanRule {
|
||||
id,
|
||||
matcher: matcher.clone(),
|
||||
created_at: now,
|
||||
expires_at: now
|
||||
.checked_add(Duration::from_secs(ttl))
|
||||
.unwrap_or(now),
|
||||
expires_at: now.checked_add(Duration::from_secs(ttl)).unwrap_or(now),
|
||||
});
|
||||
self.counters.bans_created.fetch_add(1, Ordering::Relaxed);
|
||||
Ok(InvalidationResult {
|
||||
|
||||
Reference in new issue
Block a user