fix(notify): match filters against decoded event keys (#2921)

Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
Henry Guo
2026-05-11 23:01:37 +08:00
committed by GitHub
parent 941986b331
commit 07a01a1123
3 changed files with 51 additions and 2 deletions
Generated
+1
View File
@@ -9836,6 +9836,7 @@ dependencies = [
"chrono",
"form_urlencoded",
"hashbrown 0.17.1",
"percent-encoding",
"quick-xml 0.39.4",
"rayon",
"rustc-hash",
+1
View File
@@ -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 }
+49 -2
View File
@@ -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<String> {
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<NotificationMetrics>,
@@ -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,