tiny_web/sys/workers/
fastcgi.rs

1use std::{cmp::min, collections::HashMap, sync::Arc};
2
3use percent_encoding::percent_decode_str;
4
5use crate::sys::{
6    action::ActionData,
7    request::{HttpMethod, HttpVersion, Input, RawData, Request},
8    worker::{StreamRead, StreamWrite, Worker, WorkerData},
9};
10
11/// Describes a header in a FastCGI record.
12///
13/// # Values
14///
15/// * `header_type: u8` - FastCGI header type.
16/// * `content_length: u16` - Content length.
17/// * `padding_length: u8` - Padding length.
18#[derive(Debug)]
19pub(crate) struct Header {
20    /// FastCGI header type.
21    pub header_type: u8,
22    /// Content length.
23    pub content_length: u16,
24    /// Padding length.
25    pub padding_length: u8,
26}
27
28/// Describes one record in the FastCGI protocol
29///
30/// # Values
31///
32/// * `header: Header` - FastCGI header.
33/// * `data: Vec<u8>` - Data.
34#[derive(Debug)]
35pub(crate) struct Record {
36    /// FastCGI header.
37    pub header: Header,
38    /// Data.
39    pub data: Vec<u8>,
40}
41
42/// The record type when reading it from the stream
43///
44/// # Values
45///
46/// * `Some(Record)` - Some FastCGI value.
47/// * `StreamClose` - The stream was closed.
48#[derive(Debug)]
49pub(crate) enum RecordType {
50    /// Some FastCGI value.
51    Some(Record),
52    /// The stream was closed.
53    StreamClose,
54}
55
56/// FastCGI header length
57pub const FASTCGI_HEADER_LEN: usize = 8;
58/// FastCGI max content length in the record
59pub const FASTCGI_MAX_CONTENT_LEN: usize = 65535;
60
61/// FastCGI header type BEGIN_REQUEST
62pub const FASTCGI_BEGIN_REQUEST: u8 = 1;
63/// FastCGI header type END_REQUEST
64pub const FASTCGI_END_REQUEST: u8 = 3;
65/// FastCGI header type PARAMS
66pub const FASTCGI_PARAMS: u8 = 4;
67/// FastCGI header type STDIN
68pub const FASTCGI_STDIN: u8 = 5;
69/// FastCGI header type STDOUT
70pub const FASTCGI_STDOUT: u8 = 6;
71
72/// The value of 1 in the Big Endian u16 format
73pub const U16_BE_1: [u8; 2] = u16::to_be_bytes(1);
74/// The value of 8 in the Big Endian u16 format
75pub const U16_BE_8: [u8; 2] = u16::to_be_bytes(8);
76
77/// FastCGI protocol
78pub(crate) struct Net;
79/// Alias for FastCGI protocol
80type FastCGI = Net;
81
82impl Net {
83    /// The entry point in the FastCGI protocol
84    pub async fn run(mut stream_read: StreamRead, stream_write: Arc<StreamWrite>, data: WorkerData) {
85        loop {
86            // Gets one Record
87            let record = match FastCGI::read_record_raw(&mut stream_read, 0).await {
88                RecordType::Some(r) => r,
89                RecordType::StreamClose => break,
90            };
91            // Start parsing the protocol, only if it starts with BEGIN_REQUEST
92            if FASTCGI_BEGIN_REQUEST == record.header.header_type {
93                let mut is_param_done = false;
94                let mut is_stdin_done = false;
95                let mut params = Vec::new();
96                let mut stdin = Vec::new();
97
98                // Loop until empty records PARAMS and STDIN are received
99                loop {
100                    // Gets next Record
101                    let record = match FastCGI::read_record_raw(&mut stream_read, 300).await {
102                        RecordType::Some(r) => r,
103                        RecordType::StreamClose => break,
104                    };
105                    match record.header.header_type {
106                        FASTCGI_PARAMS => {
107                            if record.data.is_empty() {
108                                is_param_done = true;
109                            } else {
110                                params.extend_from_slice(&record.data);
111                            }
112                        }
113                        FASTCGI_STDIN => {
114                            if record.data.is_empty() {
115                                is_stdin_done = true;
116                            } else {
117                                stdin.extend_from_slice(&record.data);
118                            }
119                        }
120                        _ => return,
121                    }
122                    if is_stdin_done && is_param_done {
123                        break;
124                    }
125                }
126                // Reads params
127                let (mut request, content_type, session) = FastCGI::read_param(params, Arc::clone(&data.session_key));
128
129                // Reads POST data
130                let (post, file, raw) = Worker::read_input(stdin, content_type).await;
131                request.input.file = file;
132                request.input.post = post;
133                request.input.raw = raw;
134
135                let stop = match data.stop {
136                    Some((ref rpc, stop)) => Some((Arc::clone(rpc), stop)),
137                    None => None,
138                };
139
140                let data = ActionData {
141                    engine: Arc::clone(&data.engine),
142                    lang: Arc::clone(&data.lang),
143                    html: Arc::clone(&data.html),
144                    cache: Arc::clone(&data.cache),
145                    db: Arc::clone(&data.db),
146                    session_key: Arc::clone(&data.session_key),
147                    salt: Arc::clone(&data.salt),
148                    mail: Arc::clone(&data.mail),
149                    request,
150                    session,
151                    tx: Arc::clone(&stream_write.tx),
152                    action_index: Arc::clone(&data.action_index),
153                    action_not_found: Arc::clone(&data.action_not_found),
154                    action_err: Arc::clone(&data.action_err),
155                    stop,
156                    root: Arc::clone(&data.root),
157                };
158
159                // Run main controller
160                let answer = Worker::call_action(data).await;
161                stream_write.write(answer).await;
162            } else {
163                break;
164            }
165        }
166    }
167
168    /// Read params from FastCGI record
169    ///
170    /// # Return
171    ///
172    /// * `Request` - Request struct for web engine.
173    /// * `Option<String>` - CONTENT_TYPE parameter for recognizing FASTCGI_STDIN.
174    /// * `Option<String>` - key of session.
175    fn read_param(mut data: Vec<u8>, session: Arc<String>) -> (Request, Option<String>, Option<String>) {
176        let mut params = HashMap::with_capacity(16);
177        let len = data.len();
178        let mut size = 0;
179
180        let mut ajax = false;
181        let mut host = String::new();
182        let mut scheme = "https".to_owned();
183        let mut agent = String::new();
184        let mut referer = String::new();
185        let mut ip = String::new();
186        let mut method = String::new();
187        let mut path = String::new();
188        let mut url = String::new();
189
190        let mut get = HashMap::new();
191        let mut cookie = HashMap::new();
192        let mut content_type = None;
193        let mut session_key = None;
194
195        while size < len {
196            let key_len;
197            let value_len;
198
199            // FastCGI transmits a name-value pair as the length of the name,
200            // followed by the length of the value, followed by the name, followed by the value.
201            //
202            // Lengths of 127 bytes and less can be encoded in one byte,
203            // while longer lengths are always encoded in four bytes
204
205            // We know that size < len, so tiny optimization
206            unsafe {
207                // Gets key length
208                if (*data.get_unchecked(size) >> 7) == 0 {
209                    if size + 1 > len {
210                        break;
211                    }
212                    key_len = usize::from(*data.get_unchecked(size));
213                    size += 1;
214                } else {
215                    if size + 4 > len {
216                        break;
217                    }
218                    let elem = data.get_unchecked_mut(size);
219                    *elem &= 0x7F;
220                    key_len = u32::from_be_bytes([
221                        *data.get_unchecked(size),
222                        *data.get_unchecked(size + 1),
223                        *data.get_unchecked(size + 2),
224                        *data.get_unchecked(size + 3),
225                    ]) as usize;
226                    size += 4;
227                }
228                if key_len == 0 {
229                    break;
230                }
231                // Gets value length
232                if (*data.get_unchecked(size) >> 7) == 0 {
233                    if size + 1 > len {
234                        break;
235                    }
236                    value_len = usize::from(*data.get_unchecked(size));
237                    size += 1;
238                } else {
239                    if size + 4 > len {
240                        break;
241                    }
242                    let elem = data.get_unchecked_mut(size);
243                    *elem &= 0x7F;
244                    value_len = u32::from_be_bytes([
245                        *data.get_unchecked(size),
246                        *data.get_unchecked(size + 1),
247                        *data.get_unchecked(size + 2),
248                        *data.get_unchecked(size + 3),
249                    ]) as usize;
250                    size += 4;
251                }
252                if size + key_len + value_len > len {
253                    break;
254                }
255            }
256            let key = unsafe { data.get_unchecked(size..size + key_len) };
257            size += key_len;
258            let value = match String::from_utf8(unsafe { data.get_unchecked(size..size + value_len) }.to_vec()) {
259                Ok(value) => value,
260                Err(_) => break,
261            };
262            size += value_len;
263            // We will take some of the headers right away, and leave some for the user
264            match key {
265                b"HTTP_X_REQUESTED_WITH" => ajax = value.to_lowercase().eq("xmlhttprequest"),
266                b"HTTP_HOST" => host = value,
267                b"REQUEST_SCHEME" => scheme = value,
268                b"HTTP_USER_AGENT" => agent = value,
269                b"HTTP_REFERER" => referer = value,
270                b"REMOTE_ADDR" => ip = value,
271                b"REQUEST_METHOD" => method = value,
272                b"DOCUMENT_ROOT" => path = value,
273                b"REDIRECT_URL" => {
274                    if let Some(u) = value.split('?').next() {
275                        if let Ok(u) = percent_decode_str(u).decode_utf8() {
276                            url = u.to_string();
277                        }
278                    }
279                }
280                b"QUERY_STRING" => {
281                    if !value.is_empty() {
282                        let gets: Vec<&str> = value.split('&').collect();
283                        get.reserve(gets.len());
284                        for v in gets {
285                            let key: Vec<&str> = v.splitn(2, '=').collect();
286                            match key.len() {
287                                1 => {
288                                    if let Ok(u) = percent_decode_str(v).decode_utf8() {
289                                        get.insert(u.to_string(), String::new());
290                                    }
291                                }
292                                _ => {
293                                    if let Ok(u) = percent_decode_str(unsafe { key.get_unchecked(0) }).decode_utf8() {
294                                        if let Ok(v) = percent_decode_str(unsafe { key.get_unchecked(1) }).decode_utf8() {
295                                            get.insert(u.to_string(), v.to_string());
296                                        }
297                                    }
298                                }
299                            };
300                        }
301                    }
302                }
303                b"CONTENT_TYPE" => content_type = Some(value),
304                b"HTTP_COOKIE" => {
305                    let cooks: Vec<&str> = value.split("; ").collect();
306                    cookie.reserve(cooks.len());
307                    for v in cooks {
308                        let key: Vec<&str> = v.splitn(2, '=').collect();
309                        if key.len() == 2 && *unsafe { key.get_unchecked(0) } == session.as_str() {
310                            let val = *unsafe { key.get_unchecked(1) };
311                            if val.len() == 128 {
312                                for b in val.as_bytes() {
313                                    if !((*b > 47 && *b < 58) || (*b > 96 && *b < 103)) {
314                                        continue;
315                                    }
316                                }
317                                session_key = Some((*unsafe { key.get_unchecked(1) }).to_owned());
318                            } else {
319                                cookie.insert((*unsafe { key.get_unchecked(0) }).to_owned(), (*unsafe { key.get_unchecked(1) }).to_owned());
320                            }
321                        }
322                    }
323                }
324                _ => {
325                    let key = match String::from_utf8(key.to_vec()) {
326                        Ok(key) => key,
327                        Err(_) => break,
328                    };
329                    params.insert(key, value);
330                }
331            }
332        }
333        params.shrink_to_fit();
334        let method = method.parse().unwrap_or(HttpMethod::Get);
335        let site = format!("{}://{}", scheme, host);
336        (
337            Request {
338                ajax,
339                host,
340                scheme,
341                agent,
342                referer,
343                ip,
344                method,
345                path,
346                url,
347                input: Input {
348                    get,
349                    post: HashMap::new(),
350                    file: HashMap::new(),
351                    cookie,
352                    params,
353                    raw: RawData::None,
354                },
355                site,
356                version: HttpVersion::None,
357            },
358            content_type,
359            session_key,
360        )
361    }
362
363    /// Read one record from TcpStream
364    async fn read_record_raw(stream: &mut StreamRead, timeout: u64) -> RecordType {
365        // There is not enough buffer
366        let mut buf = stream.get(stream.available());
367
368        while buf.len() < FASTCGI_HEADER_LEN {
369            if stream.read(timeout).await.is_err() {
370                return RecordType::StreamClose;
371            }
372            buf = stream.get(stream.available());
373        }
374
375        let header = FastCGI::read_header(unsafe { buf.get_unchecked(..FASTCGI_HEADER_LEN) });
376        stream.shift(FASTCGI_HEADER_LEN);
377        let mut total = header.content_length as usize;
378
379        // It is necessary to determine how much data is in the record,
380        // if it is more than FASTCGI_MAX_CONTENT_LEN, then we read in several approaches
381        let mut max_read;
382        let mut buf_len;
383        let mut vec = Vec::with_capacity(total);
384        while total > 0 {
385            max_read = min(total, stream.available());
386            while max_read == 0 {
387                if stream.read(300).await.is_err() {
388                    return RecordType::StreamClose;
389                }
390                max_read = min(total, stream.available());
391            }
392            buf = stream.get(max_read);
393            buf_len = buf.len();
394            vec.extend_from_slice(buf);
395            stream.shift(buf_len);
396            total -= buf_len;
397        }
398        stream.shift(header.padding_length as usize);
399        RecordType::Some(Record { header, data: vec })
400    }
401
402    /// Reads the FastCGI header
403    ///
404    /// # Safety
405    ///
406    /// You have to ensure that data length = FASTCGI_HEADER_LEN
407    fn read_header(data: &[u8]) -> Header {
408        unsafe {
409            Header {
410                header_type: *data.get_unchecked(1),
411                content_length: u16::from_be_bytes([*data.get_unchecked(4), *data.get_unchecked(5)]),
412                padding_length: *data.get_unchecked(6),
413            }
414        }
415    }
416
417    /// Writes answer to server
418    pub fn write(answer: Vec<u8>, end: bool) -> Vec<u8> {
419        let mut seek: usize = 0;
420        let len = answer.len();
421        let capacity = len + FASTCGI_HEADER_LEN * (4 + len / FASTCGI_MAX_CONTENT_LEN);
422
423        let mut data: Vec<u8> = Vec::with_capacity(capacity);
424        let mut size;
425
426        // The maximum record size must not exceed FASTCGI_MAX_CONTENT_LEN
427        while seek < len {
428            if seek + FASTCGI_MAX_CONTENT_LEN < len {
429                size = FASTCGI_MAX_CONTENT_LEN;
430            } else {
431                size = len - seek;
432            };
433            data.push(1_u8);
434            data.push(FASTCGI_STDOUT);
435            data.extend_from_slice(&U16_BE_1);
436            data.extend_from_slice(&u16::to_be_bytes(size as u16));
437            data.push(0);
438            data.push(0);
439            data.extend_from_slice(unsafe { answer.get_unchecked(seek..seek + size) });
440            seek += size;
441        }
442        if end {
443            // Empty FASTCGI_STDOUT
444            data.push(1_u8);
445            data.push(FASTCGI_STDOUT);
446            data.extend_from_slice(&U16_BE_1);
447            data.push(0);
448            data.push(0);
449            data.push(0);
450            data.push(0);
451
452            // Empty FASTCGI_END_REQUEST
453            data.push(1_u8);
454            data.push(FASTCGI_END_REQUEST);
455            data.extend_from_slice(&U16_BE_1);
456            data.extend_from_slice(&U16_BE_8);
457            data.push(0);
458            data.push(0);
459
460            // FASTCGI_END_REQUEST data
461            data.push(0);
462            data.push(0);
463            data.push(0);
464            data.push(0);
465            data.push(0);
466            data.push(0);
467            data.push(0);
468            data.push(0);
469        }
470        data
471    }
472}