vector/sources/util/http/
headers.rs

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