ferron/util/
fcgi_decoder.rs1use hyper::body::{Buf, Bytes};
2use tokio_util::bytes::BytesMut;
3use tokio_util::codec::Decoder;
4
5#[derive(Debug)]
6pub enum FcgiDecodedData {
7 Stdout(Bytes),
8 Stderr(Bytes),
9}
10
11enum FcgiDecodeState {
12 ReadingHead,
13 ReadingContent,
14 Finished,
15}
16
17pub struct FcgiDecoder {
18 header: Vec<u8>,
19 content_length: u16,
20 padding_length: u8,
21 state: FcgiDecodeState,
22}
23
24impl FcgiDecoder {
25 pub fn new() -> Self {
26 Self {
27 header: Vec::new(),
28 content_length: 0,
29 padding_length: 0,
30 state: FcgiDecodeState::ReadingHead,
31 }
32 }
33}
34
35impl Decoder for FcgiDecoder {
36 type Error = std::io::Error;
37 type Item = FcgiDecodedData;
38
39 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
40 loop {
41 match self.state {
42 FcgiDecodeState::ReadingHead => {
43 if src.len() >= 8 {
44 let header = &src[..8];
45 self.header = header.to_vec();
46 src.advance(8);
47 self.content_length = u16::from_be_bytes(
48 self.header[4..6]
49 .try_into()
50 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?,
51 );
52 self.padding_length = self.header[6];
53 self.state = FcgiDecodeState::ReadingContent;
54 } else {
55 return Ok(None);
56 }
57 }
58 FcgiDecodeState::ReadingContent => {
59 if src.len() >= self.content_length as usize + self.padding_length as usize {
60 let request_id = u16::from_be_bytes(
61 self.header[2..4]
62 .try_into()
63 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?,
64 );
65 let record_type = self.header[1];
66 if request_id != 1 || (record_type != 3 && record_type != 6 && record_type != 7) {
67 src.advance(self.content_length as usize + self.padding_length as usize);
69 return Ok(None);
70 }
71 let content_borrowed = &src[..(self.content_length as usize)];
72 let content = content_borrowed.to_vec();
73 src.advance(self.content_length as usize + self.padding_length as usize);
74
75 match record_type {
76 3 => {
77 if content.len() > 5 {
79 let app_status = u32::from_be_bytes(
80 content[0..4]
81 .try_into()
82 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?,
83 );
84 let protocol_status = content[4];
85 match protocol_status {
86 0 => (),
87 1 => return Err(std::io::Error::other("FastCGI server overloaded")),
88 2 => {
89 return Err(std::io::Error::other(
90 "Role not supported by the FastCGI application",
91 ))
92 }
93 3 => {
94 return Err(std::io::Error::other(
95 "Multiplexed connections not supported by the FastCGI application",
96 ))
97 }
98 _ => return Err(std::io::Error::other("Unknown error")),
99 }
100
101 self.state = FcgiDecodeState::Finished;
102 if app_status != 0 {
103 return Ok(Some(FcgiDecodedData::Stderr(Bytes::from_owner(format!(
105 "FastCGI application exited with code {app_status}"
106 )))));
107 }
108 } else {
109 return Ok(None);
111 }
112 }
113 6 => {
114 self.state = FcgiDecodeState::ReadingHead;
116 if content.is_empty() {
117 return Ok(None);
118 }
119 return Ok(Some(FcgiDecodedData::Stdout(Bytes::from_owner(content))));
120 }
121 7 => {
122 self.state = FcgiDecodeState::ReadingHead;
124 if content.is_empty() {
125 return Ok(None);
126 }
127 return Ok(Some(FcgiDecodedData::Stderr(Bytes::from_owner(content))));
128 }
129 _ => {
130 unreachable!()
132 }
133 };
134 } else {
135 return Ok(None);
136 }
137 }
138 FcgiDecodeState::Finished => {
139 src.clear();
140 return Ok(None);
141 }
142 }
143 }
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::ferron_util::fcgi_record::construct_fastcgi_record;
151 use tokio_util::bytes::BytesMut;
152 use tokio_util::codec::Decoder;
153
154 #[test]
155 fn test_fcgi_decoder_stdout() {
156 let mut decoder = FcgiDecoder::new();
157 let mut buf = BytesMut::new();
158
159 let record_type = 6;
161 let request_id = 1;
162 let content = b"Hello, FastCGI!";
163 let record = construct_fastcgi_record(record_type, request_id, content);
164
165 buf.extend_from_slice(&record);
166
167 let result = decoder.decode(&mut buf).unwrap();
168 assert!(result.is_some());
169 if let Some(FcgiDecodedData::Stdout(data)) = result {
170 assert_eq!(&data[..], content);
171 } else {
172 panic!("Expected STDOUT data");
173 }
174 }
175
176 #[test]
177 fn test_fcgi_decoder_stderr() {
178 let mut decoder = FcgiDecoder::new();
179 let mut buf = BytesMut::new();
180
181 let record_type = 7;
183 let request_id = 1;
184 let content = b"Error message";
185 let record = construct_fastcgi_record(record_type, request_id, content);
186
187 buf.extend_from_slice(&record);
188
189 let result = decoder.decode(&mut buf).unwrap();
190 assert!(result.is_some());
191 if let Some(FcgiDecodedData::Stderr(data)) = result {
192 assert_eq!(&data[..], content);
193 } else {
194 panic!("Expected STDERR data");
195 }
196 }
197
198 #[test]
199 fn test_fcgi_decoder_end_request() {
200 let mut decoder = FcgiDecoder::new();
201 let mut buf = BytesMut::new();
202
203 let record_type = 3;
205 let request_id = 1;
206 let mut content = [0u8; 4].to_vec(); content.push(0); let record = construct_fastcgi_record(record_type, request_id, &content);
209
210 buf.extend_from_slice(&record);
211
212 let result = decoder.decode(&mut buf).unwrap();
213 assert!(result.is_none()); }
215
216 #[test]
217 fn test_fcgi_decoder_invalid_record() {
218 let mut decoder = FcgiDecoder::new();
219 let mut buf = BytesMut::new();
220
221 let record_type = 6;
223 let request_id = 2; let content = b"Invalid record";
225 let record = construct_fastcgi_record(record_type, request_id, content);
226
227 buf.extend_from_slice(&record);
228
229 let result = decoder.decode(&mut buf).unwrap();
230 assert!(result.is_none()); }
232}