ferron_common/util/
header_placeholders.rs

1use crate::modules::SocketData;
2
3pub fn replace_header_placeholders(
4  input: &str,
5  request_parts: &hyper::http::request::Parts,
6  socket_data: Option<&SocketData>,
7) -> String {
8  let mut output = String::new();
9  let mut index_rb_saved = 0;
10  loop {
11    let index_lb = input[index_rb_saved..].find("{");
12    if let Some(index_lb) = index_lb {
13      let index_rb_afterlb = input[index_rb_saved + index_lb + 1..].find("}");
14      if let Some(index_rb_afterlb) = index_rb_afterlb {
15        let index_rb = index_rb_afterlb + index_lb + 1;
16        let placeholder_value = &input[index_rb_saved + index_lb + 1..index_rb_saved + index_rb];
17        output.push_str(&input[index_rb_saved..index_rb_saved + index_lb]);
18        match placeholder_value {
19          "path" => output.push_str(request_parts.uri.path()),
20          "path_and_query" => output.push_str(
21            request_parts
22              .uri
23              .path_and_query()
24              .map_or(request_parts.uri.path(), |p| p.as_str()),
25          ),
26          "method" => output.push_str(request_parts.method.as_str()),
27          "version" => output.push_str(match request_parts.version {
28            hyper::Version::HTTP_09 => "HTTP/0.9",
29            hyper::Version::HTTP_10 => "HTTP/1.0",
30            hyper::Version::HTTP_11 => "HTTP/1.1",
31            hyper::Version::HTTP_2 => "HTTP/2.0",
32            hyper::Version::HTTP_3 => "HTTP/3.0",
33            _ => "HTTP/Unknown",
34          }),
35          "scheme" => {
36            if let Some(socket_data) = socket_data {
37              output.push_str(if socket_data.encrypted { "https" } else { "http" });
38            } else {
39              // No socket data, leave it as is
40              output.push_str("{scheme}");
41            }
42          }
43          "client_ip" => {
44            if let Some(socket_data) = socket_data {
45              output.push_str(&socket_data.remote_addr.ip().to_string());
46            } else {
47              // No socket data, leave it as is
48              output.push_str("{client_ip}");
49            }
50          }
51          "client_port" => {
52            if let Some(socket_data) = socket_data {
53              output.push_str(&socket_data.remote_addr.port().to_string());
54            } else {
55              // No socket data, leave it as is
56              output.push_str("{client_port}");
57            }
58          }
59          "client_ip_canonical" => {
60            if let Some(socket_data) = socket_data {
61              output.push_str(&socket_data.remote_addr.ip().to_canonical().to_string());
62            } else {
63              // No socket data, leave it as is
64              output.push_str("{client_ip_canonical}");
65            }
66          }
67          "server_ip" => {
68            if let Some(socket_data) = socket_data {
69              output.push_str(&socket_data.local_addr.ip().to_string());
70            } else {
71              // No socket data, leave it as is
72              output.push_str("{server_ip}");
73            }
74          }
75          "server_port" => {
76            if let Some(socket_data) = socket_data {
77              output.push_str(&socket_data.local_addr.port().to_string());
78            } else {
79              // No socket data, leave it as is
80              output.push_str("{server_port}");
81            }
82          }
83          "server_ip_canonical" => {
84            if let Some(socket_data) = socket_data {
85              output.push_str(&socket_data.local_addr.ip().to_canonical().to_string());
86            } else {
87              // No socket data, leave it as is
88              output.push_str("{server_ip_canonical}");
89            }
90          }
91          _ => {
92            if let Some(header_name) = placeholder_value.strip_prefix("header:") {
93              if let Some(header_value) = request_parts.headers.get(header_name) {
94                output.push_str(header_value.to_str().unwrap_or(""));
95              }
96            } else {
97              // Unknown placeholder, leave it as is
98              output.push('{');
99              output.push_str(placeholder_value);
100              output.push('}');
101            }
102          }
103        }
104        if index_rb < input.len() - 1 {
105          index_rb_saved += index_rb + 1;
106        } else {
107          break;
108        }
109      } else {
110        output.push_str(&input[index_rb_saved..]);
111      }
112    } else {
113      output.push_str(&input[index_rb_saved..]);
114      break;
115    }
116  }
117  output
118}
119
120#[cfg(test)]
121mod tests {
122  use super::*;
123  use hyper::header::HeaderName;
124  use hyper::http::{request::Parts, Method, Version};
125  use hyper::Request;
126
127  fn make_parts(uri_str: &str, method: Method, version: Version, headers: Option<Vec<(&str, &str)>>) -> Parts {
128    let mut parts = Request::builder()
129      .uri(uri_str)
130      .method(method)
131      .version(version)
132      .body(())
133      .unwrap()
134      .into_parts()
135      .0;
136
137    if let Some(hdrs) = headers {
138      for (k, v) in hdrs {
139        parts
140          .headers
141          .insert(k.parse::<HeaderName>().unwrap(), v.parse().unwrap());
142      }
143    }
144    parts
145  }
146
147  #[test]
148  fn test_basic_placeholders() {
149    let parts = make_parts("/some/path", Method::GET, Version::HTTP_11, None);
150    let input = "Path: {path}, Method: {method}, Version: {version}";
151    let expected = "Path: /some/path, Method: GET, Version: HTTP/1.1";
152    let output = replace_header_placeholders(input, &parts, None);
153    assert_eq!(output, expected);
154  }
155
156  #[test]
157  fn test_header_placeholder() {
158    let parts = make_parts(
159      "/test",
160      Method::POST,
161      Version::HTTP_2,
162      Some(vec![("User-Agent", "MyApp/1.0")]),
163    );
164    let input = "Header: {header:User-Agent}";
165    let expected = "Header: MyApp/1.0";
166    let output = replace_header_placeholders(input, &parts, None);
167    assert_eq!(output, expected);
168  }
169
170  #[test]
171  fn test_missing_header() {
172    let parts = make_parts("/", Method::GET, Version::HTTP_11, None);
173    let input = "Header: {header:Missing}";
174    let expected = "Header: ";
175    let output = replace_header_placeholders(input, &parts, None);
176    assert_eq!(output, expected);
177  }
178
179  #[test]
180  fn test_unknown_placeholder() {
181    let parts = make_parts("/", Method::GET, Version::HTTP_11, None);
182    let input = "Unknown: {foo}";
183    let expected = "Unknown: {foo}";
184    let output = replace_header_placeholders(input, &parts, None);
185    assert_eq!(output, expected);
186  }
187
188  #[test]
189  fn test_no_placeholders() {
190    let parts = make_parts("/", Method::GET, Version::HTTP_11, None);
191    let input = "Static string with no placeholders.";
192    let output = replace_header_placeholders(input, &parts, None);
193    assert_eq!(output, input);
194  }
195
196  #[test]
197  fn test_multiple_placeholders() {
198    let parts = make_parts(
199      "/data",
200      Method::PUT,
201      Version::HTTP_2,
202      Some(vec![("Content-Type", "application/json"), ("Host", "api.example.com")]),
203    );
204    let input = "{method} {path} {version} Host: {header:Host} Content-Type: {header:Content-Type}";
205    let expected = "PUT /data HTTP/2.0 Host: api.example.com Content-Type: application/json";
206    let output = replace_header_placeholders(input, &parts, None);
207    assert_eq!(output, expected);
208  }
209}