vector/sources/util/http/
headers.rs

1use bytes::Bytes;
2use vector_lib::lookup::path;
3use vector_lib::{
4    config::{LegacyKey, LogNamespace},
5    event::Event,
6};
7use warp::http::{HeaderMap, HeaderValue};
8
9use crate::event::Value;
10use crate::sources::http_server::HttpConfigParamKind;
11
12pub fn add_headers(
13    events: &mut [Event],
14    headers_config: &[HttpConfigParamKind],
15    headers: &HeaderMap,
16    log_namespace: LogNamespace,
17    source_name: &'static str,
18) {
19    for h in headers_config {
20        match h {
21            // Add each non-wildcard containing header that was specified
22            // in the `headers` config option to the event if an exact match
23            // is found.
24            HttpConfigParamKind::Exact(header_name) => {
25                let value = headers.get(header_name).map(HeaderValue::as_bytes);
26
27                for event in events.iter_mut() {
28                    if let Event::Log(log) = event {
29                        log_namespace.insert_source_metadata(
30                            source_name,
31                            log,
32                            Some(LegacyKey::InsertIfEmpty(path!(header_name))),
33                            path!("headers", header_name),
34                            Value::from(value.map(Bytes::copy_from_slice)),
35                        );
36                    }
37                }
38            }
39            // Add all headers that match against wildcard pattens specified
40            // in the `headers` config option to the event.
41            HttpConfigParamKind::Glob(header_pattern) => {
42                for header_name in headers.keys() {
43                    if header_pattern
44                        .matches_with(header_name.as_str(), glob::MatchOptions::default())
45                    {
46                        let value = headers.get(header_name).map(HeaderValue::as_bytes);
47
48                        for event in events.iter_mut() {
49                            if let Event::Log(log) = event {
50                                log_namespace.insert_source_metadata(
51                                    source_name,
52                                    log,
53                                    Some(LegacyKey::InsertIfEmpty(path!(header_name.as_str()))),
54                                    path!("headers", header_name.as_str()),
55                                    Value::from(value.map(Bytes::copy_from_slice)),
56                                );
57                            }
58                        }
59                    }
60                }
61            }
62        };
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use crate::event::LogEvent;
69    use crate::sources::{http_server::HttpConfigParamKind, util::add_headers};
70
71    use vector_lib::config::LogNamespace;
72    use vrl::{path, value};
73    use warp::http::HeaderMap;
74
75    #[test]
76    fn multiple_headers() {
77        let header_names = [
78            HttpConfigParamKind::Exact("Content-Type".into()),
79            HttpConfigParamKind::Exact("User-Agent".into()),
80        ];
81        let mut headers = HeaderMap::new();
82        headers.insert("Content-Type", "application/x-protobuf".parse().unwrap());
83        headers.insert("User-Agent", "Test".parse().unwrap());
84        headers.insert("Content-Encoding", "gzip".parse().unwrap());
85
86        let mut base_log = [LogEvent::from(value!({})).into()];
87        add_headers(
88            &mut base_log,
89            &header_names,
90            &headers,
91            LogNamespace::Legacy,
92            "test",
93        );
94        let mut namespaced_log = [LogEvent::from(value!({})).into()];
95        add_headers(
96            &mut namespaced_log,
97            &header_names,
98            &headers,
99            LogNamespace::Vector,
100            "test",
101        );
102
103        assert_eq!(
104            base_log[0].as_log().value(),
105            namespaced_log[0]
106                .metadata()
107                .value()
108                .get(path!("test", "headers"))
109                .unwrap()
110        );
111    }
112
113    #[test]
114    fn multiple_headers_wildcard() {
115        let header_names = [HttpConfigParamKind::Glob(
116            glob::Pattern::new("Content-*").unwrap(),
117        )];
118        let mut headers = HeaderMap::new();
119        headers.insert("Content-Type", "application/x-protobuf".parse().unwrap());
120        headers.insert("User-Agent", "Test".parse().unwrap());
121        headers.insert("Content-Encoding", "gzip".parse().unwrap());
122
123        let mut base_log = [LogEvent::from(value!({})).into()];
124        add_headers(
125            &mut base_log,
126            &header_names,
127            &headers,
128            LogNamespace::Legacy,
129            "test",
130        );
131        let mut namespaced_log = [LogEvent::from(value!({})).into()];
132        add_headers(
133            &mut namespaced_log,
134            &header_names,
135            &headers,
136            LogNamespace::Vector,
137            "test",
138        );
139
140        let log = base_log[0].as_log();
141        assert_eq!(
142            log.value(),
143            namespaced_log[0]
144                .metadata()
145                .value()
146                .get(path!("test", "headers"))
147                .unwrap(),
148            "Checking legacy and namespaced log contain headers string"
149        );
150        assert_eq!(
151            log["content-type"],
152            "application/x-protobuf".into(),
153            "Checking log contains Content-Type header"
154        );
155        assert!(
156            !log.contains("user-agent"),
157            "Checking log does not contain User-Agent header"
158        );
159        assert_eq!(
160            log["content-encoding"],
161            "gzip".into(),
162            "Checking log contains Content-Encoding header"
163        );
164    }
165}