1use std::{net::SocketAddr, path::PathBuf, pin::Pin, task::Poll};
2
3use bytes::Bytes;
4use futures_util::Stream;
5use http_body::Body;
6
7use crate::CgiEnvironment;
8
9pub struct CgiBuilder {
11 inner: hashlink::LinkedHashMap<String, String>,
12 request_uri_set: bool,
13}
14
15impl CgiBuilder {
16 pub fn new() -> Self {
18 Self {
19 inner: hashlink::LinkedHashMap::new(),
20 request_uri_set: false,
21 }
22 }
23
24 pub fn var(mut self, key: String, value: String) -> Self {
26 self.inner.insert(key.to_uppercase(), value);
27 self
28 }
29
30 pub fn var_noreplace(mut self, key: String, value: String) -> Self {
32 if let hashlink::linked_hash_map::Entry::Vacant(entry) = self.inner.entry(key.to_uppercase()) {
33 entry.insert(value);
34 }
35 self
36 }
37
38 pub fn auth(mut self, auth_type: Option<String>, username: String) -> Self {
40 if let Some(auth_type) = auth_type {
41 self.inner.insert("AUTH_TYPE".to_string(), auth_type);
42 }
43 self.inner.insert("REMOTE_USER".to_string(), username);
44 self
45 }
46
47 pub fn server(mut self, server_software: String) -> Self {
49 self.inner.insert("SERVER_SOFTWARE".to_string(), server_software);
50 self
51 }
52
53 pub fn server_admin(mut self, server_admin: String) -> Self {
55 self.inner.insert("SERVER_ADMIN".to_string(), server_admin);
56 self
57 }
58
59 pub fn server_address(mut self, server_address: SocketAddr) -> Self {
61 self.inner.insert(
62 "SERVER_ADDR".to_string(),
63 server_address.ip().to_canonical().to_string(),
64 );
65 self
66 .inner
67 .insert("SERVER_PORT".to_string(), server_address.port().to_string());
68 self
69 }
70
71 pub fn client_address(mut self, client_address: SocketAddr) -> Self {
73 self.inner.insert(
74 "REMOTE_ADDR".to_string(),
75 client_address.ip().to_canonical().to_string(),
76 );
77 self
78 .inner
79 .insert("REMOTE_PORT".to_string(), client_address.port().to_string());
80 self
81 }
82
83 pub fn hostname(mut self, server_name: String) -> Self {
85 self.inner.insert("SERVER_NAME".to_string(), server_name);
86 self
87 }
88
89 pub fn script_path(mut self, script_path: PathBuf, wwwroot: PathBuf, path_info: Option<String>) -> Self {
91 self
92 .inner
93 .insert("SCRIPT_FILENAME".to_string(), script_path.to_string_lossy().to_string());
94 if let Ok(script_path) = script_path.as_path().strip_prefix(&wwwroot) {
95 self.inner.insert(
96 "SCRIPT_NAME".to_string(),
97 format!(
98 "/{}",
99 match cfg!(windows) {
100 true => script_path.to_string_lossy().to_string().replace("\\", "/"),
101 false => script_path.to_string_lossy().to_string(),
102 }
103 ),
104 );
105 }
106 self
107 .inner
108 .insert("DOCUMENT_ROOT".to_string(), wwwroot.to_string_lossy().to_string());
109 self.inner.insert(
110 "PATH_INFO".to_string(),
111 match &path_info {
112 Some(path_info) => format!("/{path_info}"),
113 None => "".to_string(),
114 },
115 );
116 self.inner.insert(
117 "PATH_TRANSLATED".to_string(),
118 match &path_info {
119 Some(path_info) => {
120 let mut path_translated = script_path.clone();
121 path_translated.push(path_info);
122 path_translated.to_string_lossy().to_string()
123 }
124 None => "".to_string(),
125 },
126 );
127 self
128 }
129
130 pub fn https(mut self) -> Self {
132 self.inner.insert("HTTPS".to_string(), "on".to_string());
133 self
134 }
135
136 pub fn request_uri(mut self, uri: &http::Uri) -> Self {
138 self.request_uri_set = true;
139 self.inner.insert(
140 "REQUEST_URI".to_string(),
141 format!(
142 "{}{}",
143 uri.path(),
144 match uri.query() {
145 Some(query) => format!("?{query}"),
146 None => String::from(""),
147 }
148 ),
149 );
150 self
151 }
152
153 pub fn system(mut self) -> Self {
155 for (key, value) in std::env::vars_os() {
156 if let Ok(key) = key.into_string() {
157 if let Ok(value) = value.into_string() {
158 self.inner.insert(key, value);
159 }
160 }
161 }
162 self
163 }
164
165 pub fn build<B>(mut self, request: http::Request<B>) -> (CgiEnvironment, CgiRequest<B>)
167 where
168 B: Body,
169 B::Data: AsRef<[u8]> + Send + 'static,
170 B::Error: Into<std::io::Error>,
171 {
172 let (parts, body) = request.into_parts();
173 self.inner.insert(
174 "QUERY_STRING".to_string(),
175 match parts.uri.query() {
176 Some(query) => query.to_string(),
177 None => "".to_string(),
178 },
179 );
180 self
181 .inner
182 .insert("REQUEST_METHOD".to_string(), parts.method.to_string());
183 if !self.request_uri_set {
184 self.inner.insert(
185 "REQUEST_URI".to_string(),
186 format!(
187 "{}{}",
188 parts.uri.path(),
189 match parts.uri.query() {
190 Some(query) => format!("?{query}"),
191 None => String::from(""),
192 }
193 ),
194 );
195 }
196 self.inner.insert(
197 "SERVER_PROTOCOL".to_string(),
198 match parts.version {
199 http::Version::HTTP_09 => "HTTP/0.9".to_string(),
200 http::Version::HTTP_10 => "HTTP/1.0".to_string(),
201 http::Version::HTTP_11 => "HTTP/1.1".to_string(),
202 http::Version::HTTP_2 => "HTTP/2.0".to_string(),
203 http::Version::HTTP_3 => "HTTP/3.0".to_string(),
204 _ => "HTTP/Unknown".to_string(),
205 },
206 );
207 for (header_name, header_value) in parts.headers.iter() {
208 let env_header_name = match *header_name {
209 http::header::CONTENT_LENGTH => "CONTENT_LENGTH".to_string(),
210 http::header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
211 _ => {
212 let mut result = String::new();
213
214 result.push_str("HTTP_");
215
216 for c in header_name.as_str().to_uppercase().chars() {
217 if c.is_alphanumeric() {
218 result.push(c);
219 } else {
220 result.push('_');
221 }
222 }
223
224 result
225 }
226 };
227 if let Some(value) = if env_header_name.starts_with("HTTP_") {
228 self.inner.get_mut(&env_header_name)
229 } else {
230 None
231 } {
232 if env_header_name == "HTTP_COOKIE" {
233 value.push_str("; ");
234 } else {
235 value.push_str(", ");
237 }
238 value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
239 } else {
240 self.inner.insert(
241 env_header_name,
242 String::from_utf8_lossy(header_value.as_bytes()).to_string(),
243 );
244 }
245 }
246 self
247 .inner
248 .insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
249 (
250 CgiEnvironment { inner: self.inner },
251 CgiRequest { body: Box::pin(body) },
252 )
253 }
254}
255
256impl Default for CgiBuilder {
257 fn default() -> Self {
258 Self::new()
259 }
260}
261
262pub struct CgiRequest<B> {
264 body: Pin<Box<B>>,
265}
266
267impl<B> Stream for CgiRequest<B>
268where
269 B: Body,
270 B::Data: AsRef<[u8]> + Send + 'static,
271 B::Error: Into<std::io::Error>,
272{
273 type Item = Result<Bytes, std::io::Error>;
274
275 fn poll_next(
276 mut self: std::pin::Pin<&mut Self>,
277 cx: &mut std::task::Context<'_>,
278 ) -> std::task::Poll<Option<Self::Item>> {
279 match Pin::new(&mut self.body).poll_frame(cx) {
280 Poll::Ready(Some(Ok(frame))) => {
281 if let Ok(data) = frame.into_data() {
282 Poll::Ready(Some(Ok(Bytes::from_owner(data))))
283 } else {
284 Poll::Ready(None)
285 }
286 }
287 Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err.into()))),
288 Poll::Ready(None) => Poll::Ready(None),
289 Poll::Pending => Poll::Pending,
290 }
291 }
292}