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    _headers: &hyper::HeaderMap,
385    _config: &ServerConfig,
386    _socket_data: &SocketData,
387    _error_logger: &ErrorLogger,
388  ) -> Result<(), Box<dyn Error + Send + Sync>> {
389    Ok(())
390  }
391
392  fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
393    false
394  }
395}
396
397#[allow(clippy::too_many_arguments)]
398async fn execute_cgi_with_environment_variables(
399  request: RequestData,
400  socket_data: &SocketData,
401  error_logger: &ErrorLogger,
402  wwwroot: &Path,
403  execute_pathbuf: PathBuf,
404  path_info: Option<String>,
405  server_administrator_email: Option<&str>,
406  cgi_interpreters: HashMap<String, Vec<String>>,
407) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
408  let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
409
410  let hyper_request = request.get_hyper_request();
411  let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
412
413  if let Some(auth_user) = request.get_auth_user() {
414    if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
415      let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
416      let mut authorization_value_split = authorization_value.split(" ");
417      if let Some(authorization_type) = authorization_value_split.next() {
418        environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
419      }
420    }
421    environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
422  }
423
424  environment_variables.insert(
425    "QUERY_STRING".to_string(),
426    match hyper_request.uri().query() {
427      Some(query) => query.to_string(),
428      None => "".to_string(),
429    },
430  );
431
432  environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
433  environment_variables.insert(
434    "SERVER_PROTOCOL".to_string(),
435    match hyper_request.version() {
436      hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
437      hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
438      hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
439      hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
440      hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
441      _ => "HTTP/Unknown".to_string(),
442    },
443  );
444  environment_variables.insert(
445    "SERVER_PORT".to_string(),
446    socket_data.local_addr.port().to_string(),
447  );
448  environment_variables.insert(
449    "SERVER_ADDR".to_string(),
450    socket_data.local_addr.ip().to_canonical().to_string(),
451  );
452  if let Some(server_administrator_email) = server_administrator_email {
453    environment_variables.insert(
454      "SERVER_ADMIN".to_string(),
455      server_administrator_email.to_string(),
456    );
457  }
458  if let Some(host) = hyper_request.headers().get(header::HOST) {
459    environment_variables.insert(
460      "SERVER_NAME".to_string(),
461      String::from_utf8_lossy(host.as_bytes()).to_string(),
462    );
463  }
464
465  environment_variables.insert(
466    "DOCUMENT_ROOT".to_string(),
467    wwwroot.to_string_lossy().to_string(),
468  );
469  environment_variables.insert(
470    "PATH_INFO".to_string(),
471    match &path_info {
472      Some(path_info) => format!("/{}", path_info),
473      None => "".to_string(),
474    },
475  );
476  environment_variables.insert(
477    "PATH_TRANSLATED".to_string(),
478    match &path_info {
479      Some(path_info) => {
480        let mut path_translated = execute_pathbuf.clone();
481        path_translated.push(path_info);
482        path_translated.to_string_lossy().to_string()
483      }
484      None => "".to_string(),
485    },
486  );
487  environment_variables.insert(
488    "REQUEST_METHOD".to_string(),
489    hyper_request.method().to_string(),
490  );
491  environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
492  environment_variables.insert(
493    "REQUEST_URI".to_string(),
494    format!(
495      "{}{}",
496      original_request_uri.path(),
497      match original_request_uri.query() {
498        Some(query) => format!("?{}", query),
499        None => String::from(""),
500      }
501    ),
502  );
503
504  environment_variables.insert(
505    "REMOTE_PORT".to_string(),
506    socket_data.remote_addr.port().to_string(),
507  );
508  environment_variables.insert(
509    "REMOTE_ADDR".to_string(),
510    socket_data.remote_addr.ip().to_canonical().to_string(),
511  );
512
513  environment_variables.insert(
514    "SCRIPT_FILENAME".to_string(),
515    execute_pathbuf.to_string_lossy().to_string(),
516  );
517  if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
518    environment_variables.insert(
519      "SCRIPT_NAME".to_string(),
520      format!(
521        "/{}",
522        match cfg!(windows) {
523          true => script_path.to_string_lossy().to_string().replace("\\", "/"),
524          false => script_path.to_string_lossy().to_string(),
525        }
526      ),
527    );
528  }
529
530  if socket_data.encrypted {
531    environment_variables.insert("HTTPS".to_string(), "ON".to_string());
532  }
533
534  for (header_name, header_value) in hyper_request.headers().iter() {
535    let env_header_name = match *header_name {
536      header::CONTENT_LENGTH => "CONTENT_LENGTH".to_string(),
537      header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
538      _ => {
539        let mut result = String::new();
540
541        result.push_str("HTTP_");
542
543        for c in header_name.as_str().to_uppercase().chars() {
544          if c.is_alphanumeric() {
545            result.push(c);
546          } else {
547            result.push('_');
548          }
549        }
550
551        result
552      }
553    };
554    if environment_variables.contains_key(&env_header_name) {
555      let value = environment_variables.get_mut(&env_header_name);
556      if let Some(value) = value {
557        if env_header_name == "HTTP_COOKIE" {
558          value.push_str("; ");
559        } else {
560          // See https://stackoverflow.com/a/1801191
561          value.push_str(", ");
562        }
563        value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
564      } else {
565        environment_variables.insert(
566          env_header_name,
567          String::from_utf8_lossy(header_value.as_bytes()).to_string(),
568        );
569      }
570    } else {
571      environment_variables.insert(
572        env_header_name,
573        String::from_utf8_lossy(header_value.as_bytes()).to_string(),
574      );
575    }
576  }
577
578  let (hyper_request, _, _, _) = request.into_parts();
579
580  execute_cgi(
581    hyper_request,
582    error_logger,
583    execute_pathbuf,
584    cgi_interpreters,
585    environment_variables,
586  )
587  .await
588}
589
590async fn execute_cgi(
591  hyper_request: HyperRequest,
592  error_logger: &ErrorLogger,
593  execute_pathbuf: PathBuf,
594  cgi_interpreters: HashMap<String, Vec<String>>,
595  environment_variables: LinkedHashMap<String, String>,
596) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
597  let (_, body) = hyper_request.into_parts();
598
599  let executable_params = match get_executable(&execute_pathbuf).await {
600    Ok(params) => params,
601    Err(err) => {
602      let contained_extension = execute_pathbuf
603        .extension()
604        .map(|a| format!(".{}", a.to_string_lossy()));
605      if let Some(contained_extension) = contained_extension {
606        if let Some(params_init) = cgi_interpreters.get(&contained_extension) {
607          let mut params: Vec<String> = params_init.iter().map(|s| s.to_owned()).collect();
608          params.push(execute_pathbuf.to_string_lossy().to_string());
609          params
610        } else {
611          Err(err)?
612        }
613      } else {
614        Err(err)?
615      }
616    }
617  };
618
619  let mut executable_params_iter = executable_params.iter();
620
621  let mut command = Command::new(match executable_params_iter.next() {
622    Some(executable_name) => executable_name,
623    None => Err(anyhow::anyhow!("Cannot determine the executable"))?,
624  });
625
626  // Set standard I/O to be piped
627  command.stdin(Stdio::piped());
628  command.stdout(Stdio::piped());
629  command.stderr(Stdio::piped());
630
631  for param in executable_params_iter {
632    command.arg(param);
633  }
634
635  command.envs(environment_variables);
636
637  let mut execute_dir_pathbuf = execute_pathbuf.clone();
638  execute_dir_pathbuf.pop();
639  command.current_dir(execute_dir_pathbuf);
640
641  let mut child = command.spawn()?;
642
643  let cgi_stdin_reader = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
644
645  let stdin = match child.stdin.take() {
646    Some(stdin) => stdin,
647    None => Err(anyhow::anyhow!(
648      "The CGI process doesn't have standard input"
649    ))?,
650  };
651  let stdout = match child.stdout.take() {
652    Some(stdout) => stdout,
653    None => Err(anyhow::anyhow!(
654      "The CGI process doesn't have standard output"
655    ))?,
656  };
657  let stderr = child.stderr.take();
658
659  let mut cgi_response = CgiResponse::new(stdout);
660
661  let stdin_copy_future = Copier::new(cgi_stdin_reader, stdin).copy();
662  let mut stdin_copy_future_pinned = Box::pin(stdin_copy_future);
663
664  let mut headers = [EMPTY_HEADER; 128];
665
666  let mut early_stdin_copied = false;
667
668  // Needed to wrap this in another scope to prevent errors with multiple mutable borrows.
669  {
670    let mut head_obtained = false;
671    let stdout_parse_future = cgi_response.get_head();
672    tokio::pin!(stdout_parse_future);
673
674    // 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
675    tokio::select! {
676      biased;
677
678      obtained_head = &mut stdout_parse_future => {
679        let obtained_head = obtained_head?;
680        if !obtained_head.is_empty() {
681          httparse::parse_headers(obtained_head, &mut headers)?;
682        }
683        head_obtained = true;
684      },
685      result = &mut stdin_copy_future_pinned => {
686        early_stdin_copied = true;
687        result?;
688      }
689    }
690
691    if !head_obtained {
692      // Kept it same as in the tokio::select macro
693      let obtained_head = stdout_parse_future.await?;
694      if !obtained_head.is_empty() {
695        httparse::parse_headers(obtained_head, &mut headers)?;
696      }
697    }
698  }
699
700  let mut response_builder = Response::builder();
701  let mut status_code = 200;
702  for header in headers {
703    if header == EMPTY_HEADER {
704      break;
705    }
706    let mut is_status_header = false;
707    match &header.name.to_lowercase() as &str {
708      "location" => {
709        if !(300..=399).contains(&status_code) {
710          status_code = 302;
711        }
712      }
713      "status" => {
714        is_status_header = true;
715        let header_value_cow = String::from_utf8_lossy(header.value);
716        let mut split_status = header_value_cow.split(" ");
717        let first_part = split_status.next();
718        if let Some(first_part) = first_part {
719          if first_part.starts_with("HTTP/") {
720            let second_part = split_status.next();
721            if let Some(second_part) = second_part {
722              if let Ok(parsed_status_code) = second_part.parse::<u16>() {
723                status_code = parsed_status_code;
724              }
725            }
726          } else if let Ok(parsed_status_code) = first_part.parse::<u16>() {
727            status_code = parsed_status_code;
728          }
729        }
730      }
731      _ => (),
732    }
733    if !is_status_header {
734      response_builder = response_builder.header(header.name, header.value);
735    }
736  }
737
738  response_builder = response_builder.status(status_code);
739
740  let reader_stream = ReaderStream::new(cgi_response);
741  let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
742  let boxed_body = stream_body.boxed();
743
744  let response = response_builder.body(boxed_body)?;
745
746  if let Some(exit_code) = child.try_wait()? {
747    if !exit_code.success() {
748      if let Some(mut stderr) = stderr {
749        let mut stderr_string = String::new();
750        stderr
751          .read_to_string(&mut stderr_string)
752          .await
753          .unwrap_or_default();
754        let stderr_string_trimmed = stderr_string.trim();
755        if !stderr_string_trimmed.is_empty() {
756          error_logger
757            .log(&format!("There were CGI errors: {}", stderr_string_trimmed))
758            .await;
759        }
760      }
761      return Ok(
762        ResponseData::builder_without_request()
763          .status(StatusCode::INTERNAL_SERVER_ERROR)
764          .build(),
765      );
766    }
767  }
768
769  let error_logger = error_logger.clone();
770
771  Ok(
772    ResponseData::builder_without_request()
773      .response(response)
774      .parallel_fn(async move {
775        if !early_stdin_copied {
776          stdin_copy_future_pinned.await.unwrap_or_default();
777        }
778
779        if let Some(mut stderr) = stderr {
780          let mut stderr_string = String::new();
781          stderr
782            .read_to_string(&mut stderr_string)
783            .await
784            .unwrap_or_default();
785          let stderr_string_trimmed = stderr_string.trim();
786          if !stderr_string_trimmed.is_empty() {
787            error_logger
788              .log(&format!("There were CGI errors: {}", stderr_string_trimmed))
789              .await;
790          }
791        }
792      })
793      .build(),
794  )
795}
796
797#[allow(dead_code)]
798#[cfg(unix)]
799async fn get_executable(
800  execute_pathbuf: &PathBuf,
801) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
802  use std::os::unix::fs::PermissionsExt;
803
804  let metadata = fs::metadata(&execute_pathbuf).await?;
805  let permissions = metadata.permissions();
806  let is_executable = permissions.mode() & 0o111 != 0;
807
808  if !is_executable {
809    Err(anyhow::anyhow!("The CGI program is not executable"))?
810  }
811
812  let executable_params_vector = vec![execute_pathbuf.to_string_lossy().to_string()];
813  Ok(executable_params_vector)
814}
815
816#[allow(dead_code)]
817#[cfg(not(unix))]
818async fn get_executable(
819  execute_pathbuf: &PathBuf,
820) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
821  use tokio::io::{AsyncBufReadExt, AsyncSeekExt, BufReader};
822
823  let mut magic_signature_buffer = [0u8; 2];
824  let mut open_file = fs::File::open(&execute_pathbuf).await?;
825  if open_file
826    .read_exact(&mut magic_signature_buffer)
827    .await
828    .is_err()
829  {
830    Err(anyhow::anyhow!("Failed to read the CGI program signature"))?
831  }
832
833  match &magic_signature_buffer {
834    b"PE" => {
835      // Windows executables
836      let executable_params_vector = vec![execute_pathbuf.to_string_lossy().to_string()];
837      Ok(executable_params_vector)
838    }
839    b"#!" => {
840      // Scripts with a shebang line
841      open_file.rewind().await?;
842      let mut buffered_file = BufReader::new(open_file);
843      let mut shebang_line = String::new();
844      buffered_file.read_line(&mut shebang_line).await?;
845
846      let mut command_begin: Vec<String> = (&shebang_line[2..])
847        .replace("\r", "")
848        .replace("\n", "")
849        .split(" ")
850        .map(|s| s.to_owned())
851        .collect();
852      command_begin.push(execute_pathbuf.to_string_lossy().to_string());
853      Ok(command_begin)
854    }
855    _ => {
856      // It's not executable
857      Err(anyhow::anyhow!("The CGI program is not executable"))?
858    }
859  }
860}