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