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 {}",
106 app_status
107 )))));
108 }
109 } else {
110 return Ok(None);
112 }
113 }
114 6 => {
115 self.state = FcgiDecodeState::ReadingHead;
117 if content.is_empty() {
118 return Ok(None);
119 }
120 return Ok(Some(FcgiDecodedData::Stdout(Bytes::from_owner(content))));
121 }
122 7 => {
123 self.state = FcgiDecodeState::ReadingHead;
125 if content.is_empty() {
126 return Ok(None);
127 }
128 return Ok(Some(FcgiDecodedData::Stderr(Bytes::from_owner(content))));
129 }
130 _ => {
131 unreachable!()
133 }
134 };
135 } else {
136 return Ok(None);
137 }
138 }
139 FcgiDecodeState::Finished => {
140 src.clear();
141 return Ok(None);
142 }
143 }
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::ferron_util::fcgi_record::construct_fastcgi_record;
152 use tokio_util::bytes::BytesMut;
153 use tokio_util::codec::Decoder;
154
155 #[test]
156 fn test_fcgi_decoder_stdout() {
157 let mut decoder = FcgiDecoder::new();
158 let mut buf = BytesMut::new();
159
160 let record_type = 6;
162 let request_id = 1;
163 let content = b"Hello, FastCGI!";
164 let record = construct_fastcgi_record(record_type, request_id, content);
165
166 buf.extend_from_slice(&record);
167
168 let result = decoder.decode(&mut buf).unwrap();
169 assert!(result.is_some());
170 if let Some(FcgiDecodedData::Stdout(data)) = result {
171 assert_eq!(&data[..], content);
172 } else {
173 panic!("Expected STDOUT data");
174 }
175 }
176
177 #[test]
178 fn test_fcgi_decoder_stderr() {
179 let mut decoder = FcgiDecoder::new();
180 let mut buf = BytesMut::new();
181
182 let record_type = 7;
184 let request_id = 1;
185 let content = b"Error message";
186 let record = construct_fastcgi_record(record_type, request_id, content);
187
188 buf.extend_from_slice(&record);
189
190 let result = decoder.decode(&mut buf).unwrap();
191 assert!(result.is_some());
192 if let Some(FcgiDecodedData::Stderr(data)) = result {
193 assert_eq!(&data[..], content);
194 } else {
195 panic!("Expected STDERR data");
196 }
197 }
198
199 #[test]
200 fn test_fcgi_decoder_end_request() {
201 let mut decoder = FcgiDecoder::new();
202 let mut buf = BytesMut::new();
203
204 let record_type = 3;
206 let request_id = 1;
207 let mut content = [0u8; 4].to_vec(); content.push(0); let record = construct_fastcgi_record(record_type, request_id, &content);
210
211 buf.extend_from_slice(&record);
212
213 let result = decoder.decode(&mut buf).unwrap();
214 assert!(result.is_none()); }
216
217 #[test]
218 fn test_fcgi_decoder_invalid_record() {
219 let mut decoder = FcgiDecoder::new();
220 let mut buf = BytesMut::new();
221
222 let record_type = 6;
224 let request_id = 2; let content = b"Invalid record";
226 let record = construct_fastcgi_record(record_type, request_id, content);
227
228 buf.extend_from_slice(&record);
229
230 let result = decoder.decode(&mut buf).unwrap();
231 assert!(result.is_none()); }
233}