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