From 07a01a11230fddbb4a0aa4c4d001592f867ff28b Mon Sep 17 00:00:00 2001 From: Henry Guo Date: Mon, 11 May 2026 23:01:37 +0800 Subject: [PATCH] fix(notify): match filters against decoded event keys (#2921) Co-authored-by: houseme --- Cargo.lock | 1 + crates/notify/Cargo.toml | 1 + crates/notify/src/notifier.rs | 51 +++++++++++++++++++++++++++++++++-- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 16844be2a..6e4b38dc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9836,6 +9836,7 @@ dependencies = [ "chrono", "form_urlencoded", "hashbrown 0.17.1", + "percent-encoding", "quick-xml 0.39.4", "rayon", "rustc-hash", diff --git a/crates/notify/Cargo.toml b/crates/notify/Cargo.toml index 16d25b6d0..07ae434d5 100644 --- a/crates/notify/Cargo.toml +++ b/crates/notify/Cargo.toml @@ -36,6 +36,7 @@ async-trait = { workspace = true } chrono = { workspace = true, features = ["serde"] } form_urlencoded = { workspace = true } hashbrown = { workspace = true } +percent-encoding = { workspace = true } rayon = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true } diff --git a/crates/notify/src/notifier.rs b/crates/notify/src/notifier.rs index aac12d1c9..7a3bd3419 100644 --- a/crates/notify/src/notifier.rs +++ b/crates/notify/src/notifier.rs @@ -12,8 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{error::NotificationError, event::Event, integration::NotificationMetrics, rules::RulesMap}; +use crate::{ + error::NotificationError, + event::Event, + integration::NotificationMetrics, + rules::{RulesMap, TargetIdSet}, +}; use hashbrown::HashMap; +use percent_encoding::percent_decode_str; use rustfs_config::notify::{DEFAULT_NOTIFY_SEND_CONCURRENCY, ENV_NOTIFY_SEND_CONCURRENCY}; use rustfs_s3_common::EventName; use rustfs_targets::Target; @@ -24,6 +30,23 @@ use std::sync::Arc; use tokio::sync::{RwLock, Semaphore}; use tracing::{debug, error, info, instrument, warn}; +fn decoded_object_key_for_matching(object_key: &str) -> Option { + if !object_key.contains('%') { + return None; + } + + let decoded = percent_decode_str(object_key).decode_utf8().ok()?; + (decoded != object_key).then(|| decoded.into_owned()) +} + +fn match_event_targets(rules: &RulesMap, event_name: EventName, object_key: &str) -> TargetIdSet { + let mut target_ids = rules.match_rules(event_name, object_key); + if let Some(decoded_key) = decoded_object_key_for_matching(object_key) { + target_ids.extend(rules.match_rules(event_name, &decoded_key)); + } + target_ids +} + /// Manages event notification to targets based on rules pub struct EventNotifier { metrics: Arc, @@ -188,7 +211,7 @@ impl EventNotifier { return; }; - let target_ids = rules.match_rules(event_name, object_key); + let target_ids = match_event_targets(&rules, event_name, object_key); if target_ids.is_empty() { debug!("No matching targets for event in bucket: {}", bucket_name); self.metrics.increment_skipped(); @@ -422,6 +445,30 @@ mod tests { atomic::{AtomicUsize, Ordering}, }; + #[test] + fn encoded_event_key_matches_raw_prefix_suffix_filter() { + let target_id = TargetID::new("primary".to_string(), "webhook".to_string()); + let mut rules_map = RulesMap::new(); + rules_map.add_rule_config(&[EventName::ObjectCreatedPut], "uploads/*.csv".to_string(), target_id.clone()); + + let targets = match_event_targets(&rules_map, EventName::ObjectCreatedPut, "uploads%2Freport.csv"); + + assert!(targets.contains(&target_id)); + } + + #[test] + fn encoded_event_key_does_not_bypass_suffix_filter() { + let target_id = TargetID::new("primary".to_string(), "webhook".to_string()); + let mut rules_map = RulesMap::new(); + rules_map.add_rule_config(&[EventName::ObjectCreatedPut], "uploads/*.csv".to_string(), target_id); + + let root_targets = match_event_targets(&rules_map, EventName::ObjectCreatedPut, "report.csv"); + let suffix_targets = match_event_targets(&rules_map, EventName::ObjectCreatedPut, "uploads%2Freport.txt"); + + assert!(root_targets.is_empty()); + assert!(suffix_targets.is_empty()); + } + #[derive(Clone)] struct TestTarget { id: TargetID,