1use std::collections::HashMap;
3use std::error::Error;
4use std::path::{Path, PathBuf};
5use std::process::Stdio;
6use std::sync::Arc;
7use std::time::Duration;
8
9use crate::ferron_common::{
10 ErrorLogger, HyperRequest, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
11 ServerModuleHandlers, SocketData,
12};
13use crate::ferron_common::{HyperUpgraded, WithRuntime};
14use async_trait::async_trait;
15use futures_util::TryStreamExt;
16use hashlink::LinkedHashMap;
17use http_body_util::{BodyExt, StreamBody};
18use httparse::EMPTY_HEADER;
19use hyper::body::Frame;
20use hyper::{header, Response, StatusCode};
21use hyper_tungstenite::HyperWebsocket;
22use tokio::fs;
23use tokio::io::AsyncReadExt;
24use tokio::process::Command;
25use tokio::runtime::Handle;
26use tokio::sync::RwLock;
27use tokio_util::io::{ReaderStream, StreamReader};
28
29use crate::ferron_res::server_software::SERVER_SOFTWARE;
30use crate::ferron_util::cgi_response::CgiResponse;
31use crate::ferron_util::ttl_cache::TtlCache;
32
33pub fn server_module_init(
34 _config: &ServerConfig,
35) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
36 let cache = Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(100))));
37 Ok(Box::new(CgiModule::new(cache)))
38}
39
40#[allow(clippy::type_complexity)]
41struct CgiModule {
42 path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>,
43}
44
45impl CgiModule {
46 #[allow(clippy::type_complexity)]
47 fn new(path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>) -> Self {
48 Self { path_cache }
49 }
50}
51
52impl ServerModule for CgiModule {
53 fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
54 Box::new(CgiModuleHandlers {
55 path_cache: self.path_cache.clone(),
56 handle,
57 })
58 }
59}
60
61#[allow(clippy::type_complexity)]
62struct CgiModuleHandlers {
63 handle: Handle,
64 path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>,
65}
66
67#[async_trait]
68impl ServerModuleHandlers for CgiModuleHandlers {
69 async fn request_handler(
70 &mut self,
71 request: RequestData,
72 config: &ServerConfig,
73 socket_data: &SocketData,
74 error_logger: &ErrorLogger,
75 ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
76 WithRuntime::new(self.handle.clone(), async move {
77 let mut cgi_script_exts = Vec::new();
78
79 let cgi_script_exts_yaml = &config["cgiScriptExtensions"];
80 if let Some(cgi_script_exts_obtained) = cgi_script_exts_yaml.as_vec() {
81 for cgi_script_ext_yaml in cgi_script_exts_obtained.iter() {
82 if let Some(cgi_script_ext) = cgi_script_ext_yaml.as_str() {
83 cgi_script_exts.push(cgi_script_ext);
84 }
85 }
86 }
87
88 if let Some(wwwroot) = config["wwwroot"].as_str() {
89 let hyper_request = request.get_hyper_request();
90
91 let request_path = hyper_request.uri().path();
92 let mut request_path_bytes = request_path.bytes();
93 if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
94 return Ok(
95 ResponseData::builder(request)
96 .status(StatusCode::BAD_REQUEST)
97 .build(),
98 );
99 }
100
101 let cache_key = format!(
102 "{}{}{}",
103 match config["ip"].as_str() {
104 Some(ip) => format!("{ip}-"),
105 None => String::from(""),
106 },
107 match config["domain"].as_str() {
108 Some(domain) => format!("{domain}-"),
109 None => String::from(""),
110 },
111 request_path
112 );
113
114 let wwwroot_unknown = PathBuf::from(wwwroot);
115 let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
116 true => wwwroot_unknown,
117 false => match fs::canonicalize(&wwwroot_unknown).await {
118 Ok(pathbuf) => pathbuf,
119 Err(_) => wwwroot_unknown,
120 },
121 };
122 let wwwroot = wwwroot_pathbuf.as_path();
123
124 let read_rwlock = self.path_cache.read().await;
125 let (execute_pathbuf, execute_path_info) = match read_rwlock.get(&cache_key) {
126 Some(data) => {
127 drop(read_rwlock);
128 data
129 }
130 None => {
131 drop(read_rwlock);
132 let mut relative_path = &request_path[1..];
133 while relative_path.as_bytes().first().copied() == Some(b'/') {
134 relative_path = &relative_path[1..];
135 }
136
137 let decoded_relative_path = match urlencoding::decode(relative_path) {
138 Ok(path) => path.to_string(),
139 Err(_) => {
140 return Ok(
141 ResponseData::builder(request)
142 .status(StatusCode::BAD_REQUEST)
143 .build(),
144 );
145 }
146 };
147
148 let joined_pathbuf = wwwroot.join(decoded_relative_path);
149 let mut execute_pathbuf: Option<PathBuf> = None;
150 let mut execute_path_info: Option<String> = None;
151
152 match fs::metadata(&joined_pathbuf).await {
153 Ok(metadata) => {
154 if metadata.is_file() {
155 let mut request_path_normalized = match cfg!(windows) {
156 true => request_path.to_lowercase(),
157 false => request_path.to_string(),
158 };
159 while request_path_normalized.contains("//") {
160 request_path_normalized = request_path_normalized.replace("//", "/");
161 }
162 if request_path_normalized == "/cgi-bin"
163 || request_path_normalized.starts_with("/cgi-bin/")
164 {
165 execute_pathbuf = Some(joined_pathbuf);
166 } else {
167 let contained_extension = joined_pathbuf
168 .extension()
169 .map(|a| format!(".{}", a.to_string_lossy()));
170 if let Some(contained_extension) = contained_extension {
171 if cgi_script_exts.contains(&(&contained_extension as &str)) {
172 execute_pathbuf = Some(joined_pathbuf);
173 }
174 }
175 }
176 } else if metadata.is_dir() {
177 let indexes = vec!["index.php", "index.cgi"];
178 for index in indexes {
179 let temp_joined_pathbuf = joined_pathbuf.join(index);
180 match fs::metadata(&temp_joined_pathbuf).await {
181 Ok(temp_metadata) => {
182 if temp_metadata.is_file() {
183 let request_path_normalized = match cfg!(windows) {
184 true => request_path.to_lowercase(),
185 false => request_path.to_string(),
186 };
187 if request_path_normalized == "/cgi-bin"
188 || request_path_normalized.starts_with("/cgi-bin/")
189 {
190 execute_pathbuf = Some(temp_joined_pathbuf);
191 break;
192 } else {
193 let contained_extension = temp_joined_pathbuf
194 .extension()
195 .map(|a| format!(".{}", a.to_string_lossy()));
196 if let Some(contained_extension) = contained_extension {
197 if cgi_script_exts.contains(&(&contained_extension as &str)) {
198 execute_pathbuf = Some(temp_joined_pathbuf);
199 break;
200 }
201 }
202 }
203 }
204 }
205 Err(_) => continue,
206 };
207 }
208 }
209 }
210 Err(err) => {
211 if err.kind() == tokio::io::ErrorKind::NotADirectory {
212 let mut temp_pathbuf = joined_pathbuf.clone();
213 loop {
214 if !temp_pathbuf.pop() {
215 break;
216 }
217 match fs::metadata(&temp_pathbuf).await {
218 Ok(metadata) => {
219 if metadata.is_file() {
220 let temp_path = temp_pathbuf.as_path();
221 if !temp_path.starts_with(wwwroot) {
222 break;
224 }
225 let path_info = match joined_pathbuf.as_path().strip_prefix(temp_path) {
226 Ok(path) => {
227 let path = path.to_string_lossy().to_string();
228 Some(match cfg!(windows) {
229 true => path.replace("\\", "/"),
230 false => path,
231 })
232 }
233 Err(_) => None,
234 };
235 let mut request_path_normalized = match cfg!(windows) {
236 true => request_path.to_lowercase(),
237 false => request_path.to_string(),
238 };
239 while request_path_normalized.contains("//") {
240 request_path_normalized = request_path_normalized.replace("//", "/");
241 }
242 if request_path_normalized == "/cgi-bin"
243 || request_path_normalized.starts_with("/cgi-bin/")
244 {
245 execute_pathbuf = Some(temp_pathbuf);
246 execute_path_info = path_info;
247 break;
248 } else {
249 let contained_extension = temp_pathbuf
250 .extension()
251 .map(|a| format!(".{}", a.to_string_lossy()));
252 if let Some(contained_extension) = contained_extension {
253 if cgi_script_exts.contains(&(&contained_extension as &str)) {
254 execute_pathbuf = Some(temp_pathbuf);
255 execute_path_info = path_info;
256 break;
257 }
258 }
259 }
260 } else {
261 break;
262 }
263 }
264 Err(err) => match err.kind() {
265 tokio::io::ErrorKind::NotADirectory => (),
266 _ => break,
267 },
268 };
269 }
270 }
271 }
272 };
273 let data = (execute_pathbuf, execute_path_info);
274
275 let mut write_rwlock = self.path_cache.write().await;
276 write_rwlock.cleanup();
277 write_rwlock.insert(cache_key, data.clone());
278 drop(write_rwlock);
279 data
280 }
281 };
282
283 if let Some(execute_pathbuf) = execute_pathbuf {
284 let mut cgi_interpreters = HashMap::new();
285 cgi_interpreters.insert(".pl".to_string(), vec!["perl".to_string()]);
286 cgi_interpreters.insert(".py".to_string(), vec!["python".to_string()]);
287 cgi_interpreters.insert(".sh".to_string(), vec!["bash".to_string()]);
288 cgi_interpreters.insert(".ksh".to_string(), vec!["ksh".to_string()]);
289 cgi_interpreters.insert(".csh".to_string(), vec!["csh".to_string()]);
290 cgi_interpreters.insert(".rb".to_string(), vec!["ruby".to_string()]);
291 cgi_interpreters.insert(".php".to_string(), vec!["php-cgi".to_string()]);
292 if cfg!(windows) {
293 cgi_interpreters.insert(".exe".to_string(), vec![]);
294 cgi_interpreters.insert(
295 ".bat".to_string(),
296 vec!["cmd".to_string(), "/c".to_string()],
297 );
298 cgi_interpreters.insert(".vbs".to_string(), vec!["cscript".to_string()]);
299 }
300
301 let cgi_interpreters_yaml = &config["cgiScriptInterpreters"];
302 if let Some(cgi_interpreters_hashmap) = cgi_interpreters_yaml.as_hash() {
303 for (key_yaml, value_yaml) in cgi_interpreters_hashmap.iter() {
304 if let Some(key) = key_yaml.as_str() {
305 if value_yaml.is_null() {
306 cgi_interpreters.remove(key);
307 } else if let Some(value) = value_yaml.as_vec() {
308 let mut params = Vec::new();
309 for param_yaml in value.iter() {
310 if let Some(param) = param_yaml.as_str() {
311 params.push(param.to_string());
312 }
313 }
314 cgi_interpreters.insert(key.to_string(), params);
315 }
316 }
317 }
318 }
319
320 return execute_cgi_with_environment_variables(
321 request,
322 socket_data,
323 error_logger,
324 wwwroot,
325 execute_pathbuf,
326 execute_path_info,
327 config["serverAdministratorEmail"].as_str(),
328 cgi_interpreters,
329 )
330 .await;
331 }
332 }
333
334 Ok(ResponseData::builder(request).build())
335 })
336 .await
337 }
338
339 async fn proxy_request_handler(
340 &mut self,
341 request: RequestData,
342 _config: &ServerConfig,
343 _socket_data: &SocketData,
344 _error_logger: &ErrorLogger,
345 ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
346 Ok(ResponseData::builder(request).build())
347 }
348
349 async fn response_modifying_handler(
350 &mut self,
351 response: HyperResponse,
352 ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
353 Ok(response)
354 }
355
356 async fn proxy_response_modifying_handler(
357 &mut self,
358 response: HyperResponse,
359 ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
360 Ok(response)
361 }
362
363 async fn connect_proxy_request_handler(
364 &mut self,
365 _upgraded_request: HyperUpgraded,
366 _connect_address: &str,
367 _config: &ServerConfig,
368 _socket_data: &SocketData,
369 _error_logger: &ErrorLogger,
370 ) -> Result<(), Box<dyn Error + Send + Sync>> {
371 Ok(())
372 }
373
374 fn does_connect_proxy_requests(&mut self) -> bool {
375 false
376 }
377
378 async fn websocket_request_handler(
379 &mut self,
380 _websocket: HyperWebsocket,
381 _uri: &hyper::Uri,
382 _headers: &hyper::HeaderMap,
383 _config: &ServerConfig,
384 _socket_data: &SocketData,
385 _error_logger: &ErrorLogger,
386 ) -> Result<(), Box<dyn Error + Send + Sync>> {
387 Ok(())
388 }
389
390 fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
391 false
392 }
393}
394
395#[allow(clippy::too_many_arguments)]
396async fn execute_cgi_with_environment_variables(
397 request: RequestData,
398 socket_data: &SocketData,
399 error_logger: &ErrorLogger,
400 wwwroot: &Path,
401 execute_pathbuf: PathBuf,
402 path_info: Option<String>,
403 server_administrator_email: Option<&str>,
404 cgi_interpreters: HashMap<String, Vec<String>>,
405) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
406 let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
407
408 let hyper_request = request.get_hyper_request();
409 let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
410
411 if let Some(auth_user) = request.get_auth_user() {
412 if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
413 let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
414 let mut authorization_value_split = authorization_value.split(" ");
415 if let Some(authorization_type) = authorization_value_split.next() {
416 environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
417 }
418 }
419 environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
420 }
421
422 environment_variables.insert(
423 "QUERY_STRING".to_string(),
424 match hyper_request.uri().query() {
425 Some(query) => query.to_string(),
426 None => "".to_string(),
427 },
428 );
429
430 environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
431 environment_variables.insert(
432 "SERVER_PROTOCOL".to_string(),
433 match hyper_request.version() {
434 hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
435 hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
436 hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
437 hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
438 hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
439 _ => "HTTP/Unknown".to_string(),
440 },
441 );
442 environment_variables.insert(
443 "SERVER_PORT".to_string(),
444 socket_data.local_addr.port().to_string(),
445 );
446 environment_variables.insert(
447 "SERVER_ADDR".to_string(),
448 socket_data.local_addr.ip().to_canonical().to_string(),
449 );
450 if let Some(server_administrator_email) = server_administrator_email {
451 environment_variables.insert(
452 "SERVER_ADMIN".to_string(),
453 server_administrator_email.to_string(),
454 );
455 }
456 if let Some(host) = hyper_request.headers().get(header::HOST) {
457 environment_variables.insert(
458 "SERVER_NAME".to_string(),
459 String::from_utf8_lossy(host.as_bytes()).to_string(),
460 );
461 }
462
463 environment_variables.insert(
464 "DOCUMENT_ROOT".to_string(),
465 wwwroot.to_string_lossy().to_string(),
466 );
467 environment_variables.insert(
468 "PATH_INFO".to_string(),
469 match &path_info {
470 Some(path_info) => format!("/{path_info}"),
471 None => "".to_string(),
472 },
473 );
474 environment_variables.insert(
475 "PATH_TRANSLATED".to_string(),
476 match &path_info {
477 Some(path_info) => {
478 let mut path_translated = execute_pathbuf.clone();
479 path_translated.push(path_info);
480 path_translated.to_string_lossy().to_string()
481 }
482 None => "".to_string(),
483 },
484 );
485 environment_variables.insert(
486 "REQUEST_METHOD".to_string(),
487 hyper_request.method().to_string(),
488 );
489 environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
490 environment_variables.insert(
491 "REQUEST_URI".to_string(),
492 format!(
493 "{}{}",
494 original_request_uri.path(),
495 match original_request_uri.query() {
496 Some(query) => format!("?{query}"),
497 None => String::from(""),
498 }
499 ),
500 );
501
502 environment_variables.insert(
503 "REMOTE_PORT".to_string(),
504 socket_data.remote_addr.port().to_string(),
505 );
506 environment_variables.insert(
507 "REMOTE_ADDR".to_string(),
508 socket_data.remote_addr.ip().to_canonical().to_string(),
509 );
510
511 environment_variables.insert(
512 "SCRIPT_FILENAME".to_string(),
513 execute_pathbuf.to_string_lossy().to_string(),
514 );
515 if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
516 environment_variables.insert(
517 "SCRIPT_NAME".to_string(),
518 format!(
519 "/{}",
520 match cfg!(windows) {
521 true => script_path.to_string_lossy().to_string().replace("\\", "/"),
522 false => script_path.to_string_lossy().to_string(),
523 }
524 ),
525 );
526 }
527
528 if socket_data.encrypted {
529 environment_variables.insert("HTTPS".to_string(), "on".to_string());
530 }
531
532 for (header_name, header_value) in hyper_request.headers().iter() {
533 let env_header_name = match *header_name {
534 header::CONTENT_LENGTH => "CONTENT_LENGTH".to_string(),
535 header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
536 _ => {
537 let mut result = String::new();
538
539 result.push_str("HTTP_");
540
541 for c in header_name.as_str().to_uppercase().chars() {
542 if c.is_alphanumeric() {
543 result.push(c);
544 } else {
545 result.push('_');
546 }
547 }
548
549 result
550 }
551 };
552 if environment_variables.contains_key(&env_header_name) {
553 let value = environment_variables.get_mut(&env_header_name);
554 if let Some(value) = value {
555 if env_header_name == "HTTP_COOKIE" {
556 value.push_str("; ");
557 } else {
558 value.push_str(", ");
560 }
561 value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
562 } else {
563 environment_variables.insert(
564 env_header_name,
565 String::from_utf8_lossy(header_value.as_bytes()).to_string(),
566 );
567 }
568 } else {
569 environment_variables.insert(
570 env_header_name,
571 String::from_utf8_lossy(header_value.as_bytes()).to_string(),
572 );
573 }
574 }
575
576 let (hyper_request, _, _, _) = request.into_parts();
577
578 execute_cgi(
579 hyper_request,
580 error_logger,
581 execute_pathbuf,
582 cgi_interpreters,
583 environment_variables,
584 )
585 .await
586}
587
588async fn execute_cgi(
589 hyper_request: HyperRequest,
590 error_logger: &ErrorLogger,
591 execute_pathbuf: PathBuf,
592 cgi_interpreters: HashMap<String, Vec<String>>,
593 environment_variables: LinkedHashMap<String, String>,
594) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
595 let (_, body) = hyper_request.into_parts();
596
597 let executable_params = match get_executable(&execute_pathbuf).await {
598 Ok(params) => params,
599 Err(err) => {
600 let contained_extension = execute_pathbuf
601 .extension()
602 .map(|a| format!(".{}", a.to_string_lossy()));
603 if let Some(contained_extension) = contained_extension {
604 if let Some(params_init) = cgi_interpreters.get(&contained_extension) {
605 let mut params: Vec<String> = params_init.iter().map(|s| s.to_owned()).collect();
606 params.push(execute_pathbuf.to_string_lossy().to_string());
607 params
608 } else {
609 Err(err)?
610 }
611 } else {
612 Err(err)?
613 }
614 }
615 };
616
617 let mut executable_params_iter = executable_params.iter();
618
619 let mut command = Command::new(match executable_params_iter.next() {
620 Some(executable_name) => executable_name,
621 None => Err(anyhow::anyhow!("Cannot determine the executable"))?,
622 });
623
624 command.stdin(Stdio::piped());
626 command.stdout(Stdio::piped());
627 command.stderr(Stdio::piped());
628
629 for param in executable_params_iter {
630 command.arg(param);
631 }
632
633 command.envs(environment_variables);
634
635 let mut execute_dir_pathbuf = execute_pathbuf.clone();
636 execute_dir_pathbuf.pop();
637 command.current_dir(execute_dir_pathbuf);
638
639 let mut child = command.spawn()?;
640
641 let cgi_stdin_reader = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
642
643 let stdin = match child.stdin.take() {
644 Some(stdin) => stdin,
645 None => Err(anyhow::anyhow!(
646 "The CGI process doesn't have standard input"
647 ))?,
648 };
649 let stdout = match child.stdout.take() {
650 Some(stdout) => stdout,
651 None => Err(anyhow::anyhow!(
652 "The CGI process doesn't have standard output"
653 ))?,
654 };
655 let stderr = child.stderr.take();
656
657 let mut cgi_response = CgiResponse::new(stdout);
658
659 let stdin_copy_future = async move {
660 let (mut cgi_stdin_reader, mut stdin) = (cgi_stdin_reader, stdin);
661 tokio::io::copy(&mut cgi_stdin_reader, &mut stdin)
662 .await
663 .map(|_| ())
664 };
665 let mut stdin_copy_future_pinned = Box::pin(stdin_copy_future);
666
667 let mut headers = [EMPTY_HEADER; 128];
668
669 let mut early_stdin_copied = false;
670
671 {
673 let mut head_obtained = false;
674 let stdout_parse_future = cgi_response.get_head();
675 tokio::pin!(stdout_parse_future);
676
677 tokio::select! {
679 biased;
680
681 obtained_head = &mut stdout_parse_future => {
682 let obtained_head = obtained_head?;
683 if !obtained_head.is_empty() {
684 httparse::parse_headers(obtained_head, &mut headers)?;
685 }
686 head_obtained = true;
687 },
688 result = &mut stdin_copy_future_pinned => {
689 early_stdin_copied = true;
690 result?;
691 }
692 }
693
694 if !head_obtained {
695 let obtained_head = stdout_parse_future.await?;
697 if !obtained_head.is_empty() {
698 httparse::parse_headers(obtained_head, &mut headers)?;
699 }
700 }
701 }
702
703 let mut response_builder = Response::builder();
704 let mut status_code = 200;
705 for header in headers {
706 if header == EMPTY_HEADER {
707 break;
708 }
709 let mut is_status_header = false;
710 match &header.name.to_lowercase() as &str {
711 "location" => {
712 if !(300..=399).contains(&status_code) {
713 status_code = 302;
714 }
715 }
716 "status" => {
717 is_status_header = true;
718 let header_value_cow = String::from_utf8_lossy(header.value);
719 let mut split_status = header_value_cow.split(" ");
720 let first_part = split_status.next();
721 if let Some(first_part) = first_part {
722 if first_part.starts_with("HTTP/") {
723 let second_part = split_status.next();
724 if let Some(second_part) = second_part {
725 if let Ok(parsed_status_code) = second_part.parse::<u16>() {
726 status_code = parsed_status_code;
727 }
728 }
729 } else if let Ok(parsed_status_code) = first_part.parse::<u16>() {
730 status_code = parsed_status_code;
731 }
732 }
733 }
734 _ => (),
735 }
736 if !is_status_header {
737 response_builder = response_builder.header(header.name, header.value);
738 }
739 }
740
741 response_builder = response_builder.status(status_code);
742
743 let reader_stream = ReaderStream::new(cgi_response);
744 let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
745 let boxed_body = stream_body.boxed();
746
747 let response = response_builder.body(boxed_body)?;
748
749 if let Some(exit_code) = child.try_wait()? {
750 if !exit_code.success() {
751 if let Some(mut stderr) = stderr {
752 let mut stderr_string = String::new();
753 stderr
754 .read_to_string(&mut stderr_string)
755 .await
756 .unwrap_or_default();
757 let stderr_string_trimmed = stderr_string.trim();
758 if !stderr_string_trimmed.is_empty() {
759 error_logger
760 .log(&format!("There were CGI errors: {stderr_string_trimmed}"))
761 .await;
762 }
763 }
764 return Ok(
765 ResponseData::builder_without_request()
766 .status(StatusCode::INTERNAL_SERVER_ERROR)
767 .build(),
768 );
769 }
770 }
771
772 let error_logger = error_logger.clone();
773
774 Ok(
775 ResponseData::builder_without_request()
776 .response(response)
777 .parallel_fn(async move {
778 if !early_stdin_copied {
779 stdin_copy_future_pinned.await.unwrap_or_default();
780 }
781
782 if let Some(mut stderr) = stderr {
783 let mut stderr_string = String::new();
784 stderr
785 .read_to_string(&mut stderr_string)
786 .await
787 .unwrap_or_default();
788 let stderr_string_trimmed = stderr_string.trim();
789 if !stderr_string_trimmed.is_empty() {
790 error_logger
791 .log(&format!("There were CGI errors: {stderr_string_trimmed}"))
792 .await;
793 }
794 }
795 })
796 .build(),
797 )
798}
799
800#[allow(dead_code)]
801#[cfg(unix)]
802async fn get_executable(
803 execute_pathbuf: &PathBuf,
804) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
805 use std::os::unix::fs::PermissionsExt;
806
807 let metadata = fs::metadata(&execute_pathbuf).await?;
808 let permissions = metadata.permissions();
809 let is_executable = permissions.mode() & 0o111 != 0;
810
811 if !is_executable {
812 Err(anyhow::anyhow!("The CGI program is not executable"))?
813 }
814
815 let executable_params_vector = vec![execute_pathbuf.to_string_lossy().to_string()];
816 Ok(executable_params_vector)
817}
818
819#[allow(dead_code)]
820#[cfg(not(unix))]
821async fn get_executable(
822 execute_pathbuf: &PathBuf,
823) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
824 use tokio::io::{AsyncBufReadExt, AsyncSeekExt, BufReader};
825
826 let mut magic_signature_buffer = [0u8; 2];
827 let mut open_file = fs::File::open(&execute_pathbuf).await?;
828 if open_file
829 .read_exact(&mut magic_signature_buffer)
830 .await
831 .is_err()
832 {
833 Err(anyhow::anyhow!("Failed to read the CGI program signature"))?
834 }
835
836 match &magic_signature_buffer {
837 b"PE" => {
838 let executable_params_vector = vec![execute_pathbuf.to_string_lossy().to_string()];
840 Ok(executable_params_vector)
841 }
842 b"#!" => {
843 open_file.rewind().await?;
845 let mut buffered_file = BufReader::new(open_file);
846 let mut shebang_line = String::new();
847 buffered_file.read_line(&mut shebang_line).await?;
848
849 let mut command_begin: Vec<String> = (&shebang_line[2..])
850 .replace("\r", "")
851 .replace("\n", "")
852 .split(" ")
853 .map(|s| s.to_owned())
854 .collect();
855 command_begin.push(execute_pathbuf.to_string_lossy().to_string());
856 Ok(command_begin)
857 }
858 _ => {
859 Err(anyhow::anyhow!("The CGI program is not executable"))?
861 }
862 }
863}