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 _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 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 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 {
670 let mut head_obtained = false;
671 let stdout_parse_future = cgi_response.get_head();
672 tokio::pin!(stdout_parse_future);
673
674 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 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 let executable_params_vector = vec![execute_pathbuf.to_string_lossy().to_string()];
837 Ok(executable_params_vector)
838 }
839 b"#!" => {
840 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 Err(anyhow::anyhow!("The CGI program is not executable"))?
858 }
859 }
860}