ferron_common/util/
header_placeholders.rs1use 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 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 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 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 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 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 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 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 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}