ferron/optional_modules/
cgi.rs

1// CGI handler code inspired by SVR.JS's RedBrick mod, translated from JavaScript to Rust.
2use 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                            // Traversed above the webroot, so ignore that.
223                            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          // See https://stackoverflow.com/a/1801191
559          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  // Set standard I/O to be piped
625  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  // Needed to wrap this in another scope to prevent errors with multiple mutable borrows.
672  {
673    let mut head_obtained = false;
674    let stdout_parse_future = cgi_response.get_head();
675    tokio::pin!(stdout_parse_future);
676
677    // Cannot use a loop with tokio::select, since stdin_copy_future_pinned being constantly ready will make the web server stop responding to HTTP requests
678    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      // Kept it same as in the tokio::select macro
696      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      // Windows executables
839      let executable_params_vector = vec![execute_pathbuf.to_string_lossy().to_string()];
840      Ok(executable_params_vector)
841    }
842    b"#!" => {
843      // Scripts with a shebang line
844      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      // It's not executable
860      Err(anyhow::anyhow!("The CGI program is not executable"))?
861    }
862  }
863}