Skip to main content

pharos_plugins/
metadata.rs

1//! Per-Query Metadata collection.
2
3use pharos_corefile::{Cursor, Error, origins_from_args_or_server_block};
4use pharos_proto::Name;
5#[cfg(feature = "plugin-kubernetes")]
6use pharos_proto::NameRef;
7use pharos_server::{Context, Next, Outcome, Plugin};
8
9/// Whether a label has a nonempty prefix and suffix around its first slash.
10#[must_use]
11pub fn is_label(label: &str) -> bool {
12    label
13        .find('/')
14        .is_some_and(|index| index > 0 && index + 1 < label.len())
15}
16
17#[derive(Debug)]
18pub struct Metadata {
19    pub domains: Vec<Name>,
20}
21
22impl Metadata {
23    #[cfg(feature = "plugin-kubernetes")]
24    pub(crate) fn matches(&self, cx: &Context<'_>) -> bool {
25        let Some(question) = cx.query.questions().next() else {
26            return false;
27        };
28        let count = question.name.labels().count();
29        self.domains.iter().any(|domain| {
30            let n = domain.label_count();
31            // RFC 4343 ยง3: DNS label comparisons ignore ASCII letter case.
32            count >= n
33                && question
34                    .name
35                    .labels()
36                    .skip(count - n)
37                    .zip(NameRef::parse(domain.as_wire(), 0).unwrap().labels())
38                    .all(|(a, b)| a.eq_ignore_ascii_case(b))
39        })
40    }
41}
42
43pub(crate) fn parse(cursor: &mut Cursor<'_>, domains: &[&str]) -> Result<Metadata, Error> {
44    cursor.next();
45    let args = cursor.remaining_args();
46    let args = args.iter().map(String::as_str).collect::<Vec<_>>();
47    let domains = origins_from_args_or_server_block(&args, domains)
48        .iter()
49        .filter_map(|domain| domain.parse().ok())
50        .collect();
51    if cursor.next_block() || cursor.next() {
52        return Err(cursor.arg_err());
53    }
54    Ok(Metadata { domains })
55}
56
57impl Plugin for Metadata {
58    async fn serve<N: Next + Send>(&self, cx: &mut Context<'_>, next: N) -> Outcome {
59        next.run(cx).await
60    }
61}