1use std::env;
4use std::error::Error;
5use std::path::{Path, PathBuf};
6
7use crate::ferron_common::{
8 ErrorLogger, HyperRequest, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
9 ServerModuleHandlers, SocketData,
10};
11use crate::ferron_common::{HyperUpgraded, WithRuntime};
12use async_trait::async_trait;
13use futures_util::TryStreamExt;
14use hashlink::LinkedHashMap;
15use http_body_util::{BodyExt, StreamBody};
16use httparse::EMPTY_HEADER;
17use hyper::body::Frame;
18use hyper::{header, Response, StatusCode};
19use hyper_tungstenite::HyperWebsocket;
20use tokio::fs;
21use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
22use tokio::net::TcpStream;
23use tokio::runtime::Handle;
24use tokio_util::io::{ReaderStream, StreamReader};
25
26use crate::ferron_res::server_software::SERVER_SOFTWARE;
27use crate::ferron_util::cgi_response::CgiResponse;
28
29pub fn server_module_init(
30 _config: &ServerConfig,
31) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
32 Ok(Box::new(ScgiModule::new()))
33}
34
35struct ScgiModule;
36
37impl ScgiModule {
38 fn new() -> Self {
39 Self
40 }
41}
42
43impl ServerModule for ScgiModule {
44 fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
45 Box::new(ScgiModuleHandlers { handle })
46 }
47}
48struct ScgiModuleHandlers {
49 handle: Handle,
50}
51
52#[async_trait]
53impl ServerModuleHandlers for ScgiModuleHandlers {
54 async fn request_handler(
55 &mut self,
56 request: RequestData,
57 config: &ServerConfig,
58 socket_data: &SocketData,
59 error_logger: &ErrorLogger,
60 ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
61 WithRuntime::new(self.handle.clone(), async move {
62 let mut scgi_to = "tcp://localhost:4000/";
63 let scgi_to_yaml = &config["scgiTo"];
64 if let Some(scgi_to_obtained) = scgi_to_yaml.as_str() {
65 scgi_to = scgi_to_obtained;
66 }
67
68 let mut scgi_path = None;
69 if let Some(scgi_path_obtained) = config["scgiPath"].as_str() {
70 scgi_path = Some(scgi_path_obtained.to_string());
71 }
72
73 let hyper_request = request.get_hyper_request();
74
75 let request_path = hyper_request.uri().path();
76 let mut request_path_bytes = request_path.bytes();
77 if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
78 return Ok(
79 ResponseData::builder(request)
80 .status(StatusCode::BAD_REQUEST)
81 .build(),
82 );
83 }
84
85 if let Some(scgi_path) = scgi_path {
86 let mut canonical_scgi_path: &str = &scgi_path;
87 if canonical_scgi_path.bytes().last() == Some(b'/') {
88 canonical_scgi_path = &canonical_scgi_path[..(canonical_scgi_path.len() - 1)];
89 }
90
91 let request_path_with_slashes = match request_path == canonical_scgi_path {
92 true => format!("{request_path}/"),
93 false => request_path.to_string(),
94 };
95 if let Some(stripped_request_path) =
96 request_path_with_slashes.strip_prefix(canonical_scgi_path)
97 {
98 let wwwroot_yaml = &config["wwwroot"];
99 let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
100
101 let wwwroot_unknown = PathBuf::from(wwwroot);
102 let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
103 true => wwwroot_unknown,
104 false => match fs::canonicalize(&wwwroot_unknown).await {
105 Ok(pathbuf) => pathbuf,
106 Err(_) => wwwroot_unknown,
107 },
108 };
109 let wwwroot = wwwroot_pathbuf.as_path();
110
111 let mut relative_path = &request_path[1..];
112 while relative_path.as_bytes().first().copied() == Some(b'/') {
113 relative_path = &relative_path[1..];
114 }
115
116 let decoded_relative_path = match urlencoding::decode(relative_path) {
117 Ok(path) => path.to_string(),
118 Err(_) => {
119 return Ok(
120 ResponseData::builder(request)
121 .status(StatusCode::BAD_REQUEST)
122 .build(),
123 );
124 }
125 };
126
127 let joined_pathbuf = wwwroot.join(decoded_relative_path);
128 let execute_pathbuf = joined_pathbuf;
129 let execute_path_info = stripped_request_path
130 .strip_prefix("/")
131 .map(|s| s.to_string());
132
133 return execute_scgi_with_environment_variables(
134 request,
135 socket_data,
136 error_logger,
137 wwwroot,
138 execute_pathbuf,
139 execute_path_info,
140 config["serverAdministratorEmail"].as_str(),
141 scgi_to,
142 )
143 .await;
144 }
145 }
146 Ok(ResponseData::builder(request).build())
147 })
148 .await
149 }
150
151 async fn proxy_request_handler(
152 &mut self,
153 request: RequestData,
154 _config: &ServerConfig,
155 _socket_data: &SocketData,
156 _error_logger: &ErrorLogger,
157 ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
158 Ok(ResponseData::builder(request).build())
159 }
160
161 async fn response_modifying_handler(
162 &mut self,
163 response: HyperResponse,
164 ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
165 Ok(response)
166 }
167
168 async fn proxy_response_modifying_handler(
169 &mut self,
170 response: HyperResponse,
171 ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
172 Ok(response)
173 }
174
175 async fn connect_proxy_request_handler(
176 &mut self,
177 _upgraded_request: HyperUpgraded,
178 _connect_address: &str,
179 _config: &ServerConfig,
180 _socket_data: &SocketData,
181 _error_logger: &ErrorLogger,
182 ) -> Result<(), Box<dyn Error + Send + Sync>> {
183 Ok(())
184 }
185
186 fn does_connect_proxy_requests(&mut self) -> bool {
187 false
188 }
189
190 async fn websocket_request_handler(
191 &mut self,
192 _websocket: HyperWebsocket,
193 _uri: &hyper::Uri,
194 _headers: &hyper::HeaderMap,
195 _config: &ServerConfig,
196 _socket_data: &SocketData,
197 _error_logger: &ErrorLogger,
198 ) -> Result<(), Box<dyn Error + Send + Sync>> {
199 Ok(())
200 }
201
202 fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
203 false
204 }
205}
206
207#[allow(clippy::too_many_arguments)]
208async fn execute_scgi_with_environment_variables(
209 request: RequestData,
210 socket_data: &SocketData,
211 error_logger: &ErrorLogger,
212 wwwroot: &Path,
213 execute_pathbuf: PathBuf,
214 path_info: Option<String>,
215 server_administrator_email: Option<&str>,
216 scgi_to: &str,
217) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
218 let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
219
220 let hyper_request = request.get_hyper_request();
221 let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
222
223 if let Some(auth_user) = request.get_auth_user() {
224 if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
225 let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
226 let mut authorization_value_split = authorization_value.split(" ");
227 if let Some(authorization_type) = authorization_value_split.next() {
228 environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
229 }
230 }
231 environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
232 }
233
234 environment_variables.insert(
235 "QUERY_STRING".to_string(),
236 match hyper_request.uri().query() {
237 Some(query) => query.to_string(),
238 None => "".to_string(),
239 },
240 );
241
242 environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
243 environment_variables.insert(
244 "SERVER_PROTOCOL".to_string(),
245 match hyper_request.version() {
246 hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
247 hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
248 hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
249 hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
250 hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
251 _ => "HTTP/Unknown".to_string(),
252 },
253 );
254 environment_variables.insert(
255 "SERVER_PORT".to_string(),
256 socket_data.local_addr.port().to_string(),
257 );
258 environment_variables.insert(
259 "SERVER_ADDR".to_string(),
260 socket_data.local_addr.ip().to_canonical().to_string(),
261 );
262 if let Some(server_administrator_email) = server_administrator_email {
263 environment_variables.insert(
264 "SERVER_ADMIN".to_string(),
265 server_administrator_email.to_string(),
266 );
267 }
268 if let Some(host) = hyper_request.headers().get(header::HOST) {
269 environment_variables.insert(
270 "SERVER_NAME".to_string(),
271 String::from_utf8_lossy(host.as_bytes()).to_string(),
272 );
273 }
274
275 environment_variables.insert(
276 "DOCUMENT_ROOT".to_string(),
277 wwwroot.to_string_lossy().to_string(),
278 );
279 environment_variables.insert(
280 "PATH_INFO".to_string(),
281 match &path_info {
282 Some(path_info) => format!("/{path_info}"),
283 None => "".to_string(),
284 },
285 );
286 environment_variables.insert(
287 "PATH_TRANSLATED".to_string(),
288 match &path_info {
289 Some(path_info) => {
290 let mut path_translated = execute_pathbuf.clone();
291 path_translated.push(path_info);
292 path_translated.to_string_lossy().to_string()
293 }
294 None => "".to_string(),
295 },
296 );
297 environment_variables.insert(
298 "REQUEST_METHOD".to_string(),
299 hyper_request.method().to_string(),
300 );
301 environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
302 environment_variables.insert("SCGI".to_string(), "1".to_string());
303 environment_variables.insert(
304 "REQUEST_URI".to_string(),
305 format!(
306 "{}{}",
307 original_request_uri.path(),
308 match original_request_uri.query() {
309 Some(query) => format!("?{query}"),
310 None => String::from(""),
311 }
312 ),
313 );
314
315 environment_variables.insert(
316 "REMOTE_PORT".to_string(),
317 socket_data.remote_addr.port().to_string(),
318 );
319 environment_variables.insert(
320 "REMOTE_ADDR".to_string(),
321 socket_data.remote_addr.ip().to_canonical().to_string(),
322 );
323
324 environment_variables.insert(
325 "SCRIPT_FILENAME".to_string(),
326 execute_pathbuf.to_string_lossy().to_string(),
327 );
328 if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
329 environment_variables.insert(
330 "SCRIPT_NAME".to_string(),
331 format!(
332 "/{}",
333 match cfg!(windows) {
334 true => script_path.to_string_lossy().to_string().replace("\\", "/"),
335 false => script_path.to_string_lossy().to_string(),
336 }
337 ),
338 );
339 }
340
341 if socket_data.encrypted {
342 environment_variables.insert("HTTPS".to_string(), "on".to_string());
343 }
344
345 let mut content_length_set = false;
346 for (header_name, header_value) in hyper_request.headers().iter() {
347 let env_header_name = match *header_name {
348 header::CONTENT_LENGTH => {
349 content_length_set = true;
350 "CONTENT_LENGTH".to_string()
351 }
352 header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
353 _ => {
354 let mut result = String::new();
355
356 result.push_str("HTTP_");
357
358 for c in header_name.as_str().to_uppercase().chars() {
359 if c.is_alphanumeric() {
360 result.push(c);
361 } else {
362 result.push('_');
363 }
364 }
365
366 result
367 }
368 };
369 if environment_variables.contains_key(&env_header_name) {
370 let value = environment_variables.get_mut(&env_header_name);
371 if let Some(value) = value {
372 if env_header_name == "HTTP_COOKIE" {
373 value.push_str("; ");
374 } else {
375 value.push_str(", ");
377 }
378 value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
379 } else {
380 environment_variables.insert(
381 env_header_name,
382 String::from_utf8_lossy(header_value.as_bytes()).to_string(),
383 );
384 }
385 } else {
386 environment_variables.insert(
387 env_header_name,
388 String::from_utf8_lossy(header_value.as_bytes()).to_string(),
389 );
390 }
391 }
392
393 if !content_length_set {
394 environment_variables.insert("CONTENT_LENGTH".to_string(), "0".to_string());
395 }
396
397 let (hyper_request, _, _, _) = request.into_parts();
398
399 execute_scgi(hyper_request, error_logger, scgi_to, environment_variables).await
400}
401
402async fn execute_scgi(
403 hyper_request: HyperRequest,
404 error_logger: &ErrorLogger,
405 scgi_to: &str,
406 mut environment_variables: LinkedHashMap<String, String>,
407) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
408 let (_, body) = hyper_request.into_parts();
409
410 for (key, value) in env::vars_os() {
412 let key_string = key.to_string_lossy().to_string();
413 let value_string = value.to_string_lossy().to_string();
414 environment_variables
415 .entry(key_string)
416 .or_insert(value_string);
417 }
418
419 let scgi_to_fixed = if let Some(stripped) = scgi_to.strip_prefix("unix:///") {
420 &format!("unix://ignore/{stripped}")
422 } else {
423 scgi_to
424 };
425
426 let scgi_to_url = scgi_to_fixed.parse::<hyper::Uri>()?;
427 let scheme_str = scgi_to_url.scheme_str();
428
429 let (socket_reader, mut socket_writer) = match scheme_str {
430 Some("tcp") => {
431 let host = match scgi_to_url.host() {
432 Some(host) => host,
433 None => Err(anyhow::anyhow!("The SCGI URL doesn't include the host"))?,
434 };
435
436 let port = match scgi_to_url.port_u16() {
437 Some(port) => port,
438 None => Err(anyhow::anyhow!("The SCGI URL doesn't include the port"))?,
439 };
440
441 let addr = format!("{host}:{port}");
442
443 match connect_tcp(&addr).await {
444 Ok(data) => data,
445 Err(err) => match err.kind() {
446 tokio::io::ErrorKind::ConnectionRefused
447 | tokio::io::ErrorKind::NotFound
448 | tokio::io::ErrorKind::HostUnreachable => {
449 error_logger
450 .log(&format!("Service unavailable: {err}"))
451 .await;
452 return Ok(
453 ResponseData::builder_without_request()
454 .status(StatusCode::SERVICE_UNAVAILABLE)
455 .build(),
456 );
457 }
458 _ => Err(err)?,
459 },
460 }
461 }
462 Some("unix") => {
463 let path = scgi_to_url.path();
464 match connect_unix(path).await {
465 Ok(data) => data,
466 Err(err) => match err.kind() {
467 tokio::io::ErrorKind::ConnectionRefused
468 | tokio::io::ErrorKind::NotFound
469 | tokio::io::ErrorKind::HostUnreachable => {
470 error_logger
471 .log(&format!("Service unavailable: {err}"))
472 .await;
473 return Ok(
474 ResponseData::builder_without_request()
475 .status(StatusCode::SERVICE_UNAVAILABLE)
476 .build(),
477 );
478 }
479 _ => Err(err)?,
480 },
481 }
482 }
483 _ => Err(anyhow::anyhow!(
484 "Only HTTP and HTTPS reverse proxy URLs are supported."
485 ))?,
486 };
487
488 let mut environment_variables_to_wrap = Vec::new();
490 for (key, value) in environment_variables.iter() {
491 let mut environment_variable = Vec::new();
492 environment_variable.extend_from_slice(key.as_bytes());
493 environment_variable.push(b'\0');
494 environment_variable.extend_from_slice(value.as_bytes());
495 environment_variable.push(b'\0');
496 if key == "CONTENT_LENGTH" {
497 environment_variable.append(&mut environment_variables_to_wrap);
498 environment_variables_to_wrap = environment_variable;
499 } else {
500 environment_variables_to_wrap.append(&mut environment_variable);
501 }
502 }
503
504 let environment_variables_to_wrap_length = environment_variables_to_wrap.len();
505 let mut environment_variables_netstring = Vec::new();
506 environment_variables_netstring
507 .extend_from_slice(environment_variables_to_wrap_length.to_string().as_bytes());
508 environment_variables_netstring.push(b':');
509 environment_variables_netstring.append(&mut environment_variables_to_wrap);
510 environment_variables_netstring.push(b',');
511
512 socket_writer
514 .write_all(&environment_variables_netstring)
515 .await?;
516
517 let cgi_stdin_reader = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
518
519 let stdin = socket_writer;
522 let stdout = socket_reader;
523
524 let mut cgi_response = CgiResponse::new(stdout);
525
526 let stdin_copy_future = async move {
527 let (mut cgi_stdin_reader, mut stdin) = (cgi_stdin_reader, stdin);
528 tokio::io::copy(&mut cgi_stdin_reader, &mut stdin)
529 .await
530 .map(|_| ())
531 };
532 let mut stdin_copy_future_pinned = Box::pin(stdin_copy_future);
533
534 let mut headers = [EMPTY_HEADER; 128];
535
536 let mut early_stdin_copied = false;
537
538 {
540 let mut head_obtained = false;
541 let stdout_parse_future = cgi_response.get_head();
542 tokio::pin!(stdout_parse_future);
543
544 tokio::select! {
546 biased;
547
548 obtained_head = &mut stdout_parse_future => {
549 let obtained_head = obtained_head?;
550 if !obtained_head.is_empty() {
551 httparse::parse_headers(obtained_head, &mut headers)?;
552 }
553 head_obtained = true;
554 },
555 result = &mut stdin_copy_future_pinned => {
556 early_stdin_copied = true;
557 result?;
558 }
559 }
560
561 if !head_obtained {
562 let obtained_head = stdout_parse_future.await?;
564 if !obtained_head.is_empty() {
565 httparse::parse_headers(obtained_head, &mut headers)?;
566 }
567 }
568 }
569
570 let mut response_builder = Response::builder();
571 let mut status_code = 200;
572 for header in headers {
573 if header == EMPTY_HEADER {
574 break;
575 }
576 let mut is_status_header = false;
577 match &header.name.to_lowercase() as &str {
578 "location" => {
579 if !(300..=399).contains(&status_code) {
580 status_code = 302;
581 }
582 }
583 "status" => {
584 is_status_header = true;
585 let header_value_cow = String::from_utf8_lossy(header.value);
586 let mut split_status = header_value_cow.split(" ");
587 let first_part = split_status.next();
588 if let Some(first_part) = first_part {
589 if first_part.starts_with("HTTP/") {
590 let second_part = split_status.next();
591 if let Some(second_part) = second_part {
592 if let Ok(parsed_status_code) = second_part.parse::<u16>() {
593 status_code = parsed_status_code;
594 }
595 }
596 } else if let Ok(parsed_status_code) = first_part.parse::<u16>() {
597 status_code = parsed_status_code;
598 }
599 }
600 }
601 _ => (),
602 }
603 if !is_status_header {
604 response_builder = response_builder.header(header.name, header.value);
605 }
606 }
607
608 response_builder = response_builder.status(status_code);
609
610 let reader_stream = ReaderStream::new(cgi_response);
611 let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
612 let boxed_body = stream_body.boxed();
613
614 let response = response_builder.body(boxed_body)?;
615
616 Ok(
617 ResponseData::builder_without_request()
618 .response(response)
619 .parallel_fn(async move {
620 if !early_stdin_copied {
621 stdin_copy_future_pinned.await.unwrap_or_default();
622 }
623 })
624 .build(),
625 )
626}
627
628async fn connect_tcp(
629 addr: &str,
630) -> Result<
631 (
632 Box<dyn AsyncRead + Send + Sync + Unpin>,
633 Box<dyn AsyncWrite + Send + Sync + Unpin>,
634 ),
635 tokio::io::Error,
636> {
637 let socket = TcpStream::connect(addr).await?;
638 socket.set_nodelay(true)?;
639
640 let (socket_reader_set, socket_writer_set) = tokio::io::split(socket);
641 Ok((Box::new(socket_reader_set), Box::new(socket_writer_set)))
642}
643
644#[allow(dead_code)]
645#[cfg(unix)]
646async fn connect_unix(
647 path: &str,
648) -> Result<
649 (
650 Box<dyn AsyncRead + Send + Sync + Unpin>,
651 Box<dyn AsyncWrite + Send + Sync + Unpin>,
652 ),
653 tokio::io::Error,
654> {
655 use tokio::net::UnixStream;
656
657 let socket = UnixStream::connect(path).await?;
658
659 let (socket_reader_set, socket_writer_set) = tokio::io::split(socket);
660 Ok((Box::new(socket_reader_set), Box::new(socket_writer_set)))
661}
662
663#[allow(dead_code)]
664#[cfg(not(unix))]
665async fn connect_unix(
666 _path: &str,
667) -> Result<
668 (
669 Box<dyn AsyncRead + Send + Sync + Unpin>,
670 Box<dyn AsyncWrite + Send + Sync + Unpin>,
671 ),
672 tokio::io::Error,
673> {
674 Err(tokio::io::Error::new(
675 tokio::io::ErrorKind::Unsupported,
676 "Unix sockets are not supports on non-Unix platforms.",
677 ))
678}