1use std::collections::HashMap;
3use std::error::Error;
4use std::path::{Path, PathBuf};
5use std::process::Stdio;
6use std::sync::Arc;
7use std::time::Duration;
8
9use crate::ferron_common::{
10 ErrorLogger, HyperRequest, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
11 ServerModuleHandlers, SocketData,
12};
13use crate::ferron_common::{HyperUpgraded, WithRuntime};
14use async_trait::async_trait;
15use futures_util::TryStreamExt;
16use hashlink::LinkedHashMap;
17use http_body_util::{BodyExt, StreamBody};
18use httparse::EMPTY_HEADER;
19use hyper::body::Frame;
20use hyper::{header, Response, StatusCode};
21use hyper_tungstenite::HyperWebsocket;
22use tokio::fs;
23use tokio::io::AsyncReadExt;
24use tokio::process::Command;
25use tokio::runtime::Handle;
26use tokio::sync::RwLock;
27use tokio_util::io::{ReaderStream, StreamReader};
28
29use crate::ferron_res::server_software::SERVER_SOFTWARE;
30use crate::ferron_util::cgi_response::CgiResponse;
31use crate::ferron_util::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 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 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 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 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 {
669 let mut head_obtained = false;
670 let stdout_parse_future = cgi_response.get_head();
671 tokio::pin!(stdout_parse_future);
672
673 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 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 let executable_params_vector = vec![execute_pathbuf.to_string_lossy().to_string()];
836 Ok(executable_params_vector)
837 }
838 b"#!" => {
839 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 Err(anyhow::anyhow!("The CGI program is not executable"))?
857 }
858 }
859}