fcgi_client/
meta.rs

1// Copyright 2022 jmjoy
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Internal FastCGI protocol metadata structures and parsing.
16//!
17//! This module contains the internal structures and constants used
18//! for parsing and generating FastCGI protocol messages.
19
20use crate::{
21    error::{ClientError, ClientResult},
22    Params,
23};
24use bytes::{Buf, BufMut, Bytes, BytesMut};
25use std::{
26    borrow::Cow,
27    cmp::min,
28    collections::HashMap,
29    fmt::{self, Debug, Display},
30    mem::size_of,
31    ops::{Deref, DerefMut},
32};
33use tokio::io::{self, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
34
35/// FastCGI protocol version 1
36pub(crate) const VERSION_1: u8 = 1;
37/// Maximum length for FastCGI content
38pub(crate) const MAX_LENGTH: usize = 0xffff;
39/// Length of FastCGI header in bytes
40pub(crate) const HEADER_LEN: usize = size_of::<Header>();
41
42/// FastCGI request types as defined in the protocol specification.
43#[derive(Debug, Clone, Copy)]
44#[repr(u8)]
45pub enum RequestType {
46    /// Begin request record type
47    BeginRequest = 1,
48    /// Abort request record type
49    AbortRequest = 2,
50    /// End request record type
51    EndRequest = 3,
52    /// Parameters record type
53    Params = 4,
54    /// Stdin record type
55    Stdin = 5,
56    /// Stdout record type
57    Stdout = 6,
58    /// Stderr record type
59    Stderr = 7,
60    /// Data record type
61    Data = 8,
62    /// Get values record type
63    GetValues = 9,
64    /// Get values result record type
65    GetValuesResult = 10,
66    /// Unknown type record type
67    UnknownType = 11,
68}
69
70impl RequestType {
71    /// Converts a u8 value to RequestType.
72    ///
73    /// # Arguments
74    ///
75    /// * `u` - The numeric value to convert
76    fn from_u8(u: u8) -> Self {
77        match u {
78            1 => RequestType::BeginRequest,
79            2 => RequestType::AbortRequest,
80            3 => RequestType::EndRequest,
81            4 => RequestType::Params,
82            5 => RequestType::Stdin,
83            6 => RequestType::Stdout,
84            7 => RequestType::Stderr,
85            8 => RequestType::Data,
86            9 => RequestType::GetValues,
87            10 => RequestType::GetValuesResult,
88            _ => RequestType::UnknownType,
89        }
90    }
91}
92
93impl Display for RequestType {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
95        write!(f, "{}", *self as u8)
96    }
97}
98
99#[derive(Debug, Clone)]
100pub(crate) struct Header {
101    /// FastCGI protocol version
102    pub(crate) version: u8,
103    /// Type of the FastCGI record
104    pub(crate) r#type: RequestType,
105    /// Request ID for this record
106    pub(crate) request_id: u16,
107    /// Length of the content data
108    pub(crate) content_length: u16,
109    /// Length of padding data
110    pub(crate) padding_length: u8,
111    /// Reserved byte
112    pub(crate) reserved: u8,
113}
114
115impl Header {
116    /// Writes data to a stream in batches with proper FastCGI headers.
117    ///
118    /// # Arguments
119    ///
120    /// * `r#type` - The type of FastCGI record
121    /// * `request_id` - The request ID
122    /// * `writer` - The writer to write to
123    /// * `content` - The content to write
124    /// * `before_write` - Optional callback to modify header before writing
125    pub(crate) async fn write_to_stream_batches<F, R, W>(
126        r#type: RequestType, request_id: u16, writer: &mut W, content: &mut R,
127        before_write: Option<F>,
128    ) -> io::Result<()>
129    where
130        F: Fn(Header) -> Header,
131        R: AsyncRead + Unpin,
132        W: AsyncWrite + Unpin,
133    {
134        let mut buf = vec![0u8; MAX_LENGTH];
135        let mut had_written = false;
136
137        loop {
138            let read = content.read(&mut buf).await?;
139            if had_written && read == 0 {
140                break;
141            }
142
143            let buf = &buf[..read];
144            let mut header = Self::new(r#type.clone(), request_id, buf);
145            if let Some(ref f) = before_write {
146                header = f(header);
147            }
148            header.write_to_stream(writer, buf).await?;
149
150            had_written = true;
151        }
152        Ok(())
153    }
154
155    /// Creates a new header with given parameters.
156    ///
157    /// # Arguments
158    ///
159    /// * `r#type` - The type of FastCGI record
160    /// * `request_id` - The request ID
161    /// * `content` - The content data
162    fn new(r#type: RequestType, request_id: u16, content: &[u8]) -> Self {
163        let content_length = min(content.len(), MAX_LENGTH) as u16;
164        Self {
165            version: VERSION_1,
166            r#type,
167            request_id,
168            content_length,
169            padding_length: (-(content_length as i16) & 7) as u8,
170            reserved: 0,
171        }
172    }
173
174    /// Writes the header and content to a stream.
175    ///
176    /// # Arguments
177    ///
178    /// * `writer` - The writer to write to
179    /// * `content` - The content to write
180    async fn write_to_stream<W: AsyncWrite + Unpin>(
181        self, writer: &mut W, content: &[u8],
182    ) -> io::Result<()> {
183        let mut buf: Bytes = (&self).into();
184
185        writer.write_all_buf(&mut buf).await?;
186        writer.write_all(content).await?;
187
188        if self.padding_length > 0 {
189            let padding = [0u8; 7]; // Max padding is 7 bytes
190            writer
191                .write_all(&padding[..self.padding_length as usize])
192                .await?;
193        }
194        Ok(())
195    }
196
197    /// Creates a new header by reading from a stream.
198    ///
199    /// # Arguments
200    ///
201    /// * `reader` - The reader to read from
202    pub(crate) async fn new_from_stream<R: AsyncRead + Unpin>(reader: &mut R) -> io::Result<Self> {
203        let mut buf = BytesMut::zeroed(HEADER_LEN);
204        reader.read_exact(&mut buf).await?;
205        Ok(Self::from(buf))
206    }
207
208    /// Reads content from a stream based on the header's content length.
209    ///
210    /// # Arguments
211    ///
212    /// * `reader` - The reader to read from
213    pub(crate) async fn read_content_from_stream<R: AsyncRead + Unpin>(
214        &self, reader: &mut R,
215    ) -> io::Result<BytesMut> {
216        let mut buf = BytesMut::zeroed(self.content_length as usize);
217        reader.read_exact(&mut buf).await?;
218        let mut padding_buf = BytesMut::zeroed(self.padding_length as usize);
219        reader.read_exact(&mut padding_buf).await?;
220        Ok(buf)
221    }
222}
223
224impl Into<Bytes> for &Header {
225    fn into(self) -> Bytes {
226        let mut buf = BytesMut::with_capacity(HEADER_LEN);
227        buf.put_u8(self.version);
228        buf.put_u8(self.r#type as u8);
229        buf.put_u16(self.request_id);
230        buf.put_u16(self.content_length);
231        buf.put_u8(self.padding_length);
232        buf.put_u8(self.reserved);
233        buf.freeze()
234    }
235}
236
237impl From<BytesMut> for Header {
238    /// Creates a new header from a buffer.
239    ///
240    /// # Arguments
241    ///
242    /// * `buf` - The buffer containing header data
243    fn from(mut buf: BytesMut) -> Self {
244        Self {
245            version: buf.get_u8(),
246            r#type: RequestType::from_u8(buf.get_u8()),
247            request_id: buf.get_u16(),
248            content_length: buf.get_u16(),
249            padding_length: buf.get_u8(),
250            reserved: buf.get_u8(),
251        }
252    }
253}
254
255/// FastCGI application roles.
256#[derive(Debug, Clone, Copy)]
257#[repr(u16)]
258#[allow(dead_code)]
259pub enum Role {
260    /// Responder role - handles requests and returns responses
261    Responder = 1,
262    /// Authorizer role - performs authorization checks
263    Authorizer = 2,
264    /// Filter role - filters data between web server and application
265    Filter = 3,
266}
267
268/// Begin request record body data.
269#[derive(Debug)]
270pub(crate) struct BeginRequest {
271    /// The role of the application
272    pub(crate) role: Role,
273    /// Flags byte (bit 0 = keep alive flag)
274    pub(crate) flags: u8,
275    /// Reserved bytes
276    pub(crate) reserved: [u8; 5],
277}
278
279impl BeginRequest {
280    /// Creates a new begin request record.
281    ///
282    /// # Arguments
283    ///
284    /// * `role` - The role of the application
285    /// * `keep_alive` - Whether to keep the connection alive
286    pub(crate) fn new(role: Role, keep_alive: bool) -> Self {
287        Self {
288            role,
289            flags: keep_alive as u8,
290            reserved: [0; 5],
291        }
292    }
293
294    /// Converts the begin request to bytes.
295    pub(crate) fn to_content(&self) -> BytesMut {
296        let mut buf = BytesMut::with_capacity(8);
297        buf.put_u16(self.role as u16);
298        buf.put_u8(self.flags);
299        buf.put_slice(&self.reserved);
300        buf
301    }
302}
303
304/// Complete begin request record with header and content.
305#[derive(Debug)]
306pub(crate) struct BeginRequestRec {
307    /// The FastCGI header
308    pub(crate) header: Header,
309    /// The begin request data
310    pub(crate) begin_request: BeginRequest,
311    /// The serialized content
312    pub(crate) content: BytesMut,
313}
314
315impl BeginRequestRec {
316    /// Creates a new begin request record.
317    ///
318    /// # Arguments
319    ///
320    /// * `request_id` - The request ID
321    /// * `role` - The role of the application
322    /// * `keep_alive` - Whether to keep the connection alive
323    pub(crate) fn new(request_id: u16, role: Role, keep_alive: bool) -> Self {
324        let begin_request = BeginRequest::new(role, keep_alive);
325        let content = begin_request.to_content();
326        let header = Header::new(RequestType::BeginRequest, request_id, &content);
327        Self {
328            header,
329            begin_request,
330            content,
331        }
332    }
333
334    /// Writes the begin request record to a stream.
335    ///
336    /// # Arguments
337    ///
338    /// * `writer` - The writer to write to
339    pub(crate) async fn write_to_stream<W: AsyncWrite + Unpin>(
340        self, writer: &mut W,
341    ) -> io::Result<()> {
342        self.header.write_to_stream(writer, &self.content).await
343    }
344}
345
346/// Parameter length encoding for FastCGI.
347#[derive(Debug, Clone, Copy)]
348pub enum ParamLength {
349    /// Short length (0-127 bytes)
350    Short(u8),
351    /// Long length (128+ bytes)
352    Long(u32),
353}
354
355impl ParamLength {
356    /// Creates a new parameter length encoding.
357    ///
358    /// # Arguments
359    ///
360    /// * `length` - The length to encode
361    pub fn new(length: usize) -> Self {
362        if length < 128 {
363            ParamLength::Short(length as u8)
364        } else {
365            let mut length = length;
366            length |= 1 << 31;
367            ParamLength::Long(length as u32)
368        }
369    }
370
371    /// Converts the parameter length to bytes.
372    pub fn content(self) -> BytesMut {
373        match self {
374            ParamLength::Short(l) => {
375                let mut buf = BytesMut::with_capacity(1);
376                buf.put_u8(l);
377                buf
378            }
379            ParamLength::Long(l) => {
380                let mut buf = BytesMut::with_capacity(4);
381                buf.put_u32(l);
382                buf
383            }
384        }
385    }
386}
387
388/// A single parameter name-value pair.
389#[derive(Debug)]
390pub struct ParamPair<'a> {
391    /// Length of the parameter name
392    name_length: ParamLength,
393    /// Length of the parameter value
394    value_length: ParamLength,
395    /// The parameter name
396    name_data: Cow<'a, str>,
397    /// The parameter value
398    value_data: Cow<'a, str>,
399}
400
401impl<'a> ParamPair<'a> {
402    /// Creates a new parameter pair.
403    ///
404    /// # Arguments
405    ///
406    /// * `name` - The parameter name
407    /// * `value` - The parameter value
408    fn new(name: Cow<'a, str>, value: Cow<'a, str>) -> Self {
409        let name_length = ParamLength::new(name.len());
410        let value_length = ParamLength::new(value.len());
411        Self {
412            name_length,
413            value_length,
414            name_data: name,
415            value_data: value,
416        }
417    }
418
419    /// Writes the parameter pair to a buffer.
420    ///
421    /// # Arguments
422    ///
423    /// * `buf` - The buffer to write to
424    fn write_to_buf(&self, buf: &mut BytesMut) {
425        let name_len = self.name_length.content();
426        buf.extend_from_slice(&name_len);
427        let value_len = self.value_length.content();
428        buf.extend_from_slice(&value_len);
429        buf.extend_from_slice(self.name_data.as_bytes());
430        buf.extend_from_slice(self.value_data.as_bytes());
431    }
432}
433
434/// Collection of parameter pairs.
435#[derive(Debug)]
436pub(crate) struct ParamPairs<'a>(Vec<ParamPair<'a>>);
437
438impl<'a> ParamPairs<'a> {
439    /// Creates parameter pairs from a Params object.
440    ///
441    /// # Arguments
442    ///
443    /// * `params` - The parameters to convert
444    pub(crate) fn new(params: Params<'a>) -> Self {
445        let mut param_pairs = Vec::new();
446        let params: HashMap<Cow<'a, str>, Cow<'a, str>> = params.into();
447        for (name, value) in params.into_iter() {
448            let param_pair = ParamPair::new(name, value);
449            param_pairs.push(param_pair);
450        }
451
452        Self(param_pairs)
453    }
454
455    /// Converts the parameter pairs to bytes.
456    pub(crate) fn to_content(&self) -> Bytes {
457        let mut buf = BytesMut::new();
458
459        for param_pair in self.iter() {
460            param_pair.write_to_buf(&mut buf);
461        }
462
463        buf.freeze()
464    }
465}
466
467impl<'a> Deref for ParamPairs<'a> {
468    type Target = Vec<ParamPair<'a>>;
469
470    fn deref(&self) -> &Self::Target {
471        &self.0
472    }
473}
474
475impl<'a> DerefMut for ParamPairs<'a> {
476    fn deref_mut(&mut self) -> &mut Self::Target {
477        &mut self.0
478    }
479}
480
481/// FastCGI protocol status codes.
482#[derive(Debug)]
483#[repr(u8)]
484pub enum ProtocolStatus {
485    /// Request completed successfully
486    RequestComplete = 0,
487    /// This app can't multiplex connections
488    CantMpxConn = 1,
489    /// New request rejected; too busy
490    Overloaded = 2,
491    /// Role value not known
492    UnknownRole = 3,
493}
494
495impl ProtocolStatus {
496    /// Converts a u8 value to ProtocolStatus.
497    ///
498    /// # Arguments
499    ///
500    /// * `u` - The numeric value to convert
501    pub fn from_u8(u: u8) -> Self {
502        match u {
503            0 => ProtocolStatus::RequestComplete,
504            1 => ProtocolStatus::CantMpxConn,
505            2 => ProtocolStatus::Overloaded,
506            _ => ProtocolStatus::UnknownRole,
507        }
508    }
509
510    /// Converts the protocol status to a client result.
511    ///
512    /// # Arguments
513    ///
514    /// * `app_status` - The application status code
515    pub(crate) fn convert_to_client_result(self, app_status: u32) -> ClientResult<()> {
516        match self {
517            ProtocolStatus::RequestComplete => Ok(()),
518            _ => Err(ClientError::new_end_request_with_protocol_status(
519                self, app_status,
520            )),
521        }
522    }
523}
524
525/// End request record body data.
526#[derive(Debug)]
527pub struct EndRequest {
528    /// The application status code
529    pub(crate) app_status: u32,
530    /// The protocol status
531    pub(crate) protocol_status: ProtocolStatus,
532    /// Reserved bytes
533    #[allow(dead_code)]
534    reserved: [u8; 3],
535}
536
537impl From<BytesMut> for EndRequest {
538    fn from(mut buf: BytesMut) -> Self {
539        let app_status = buf.get_u32();
540        let protocol_status = ProtocolStatus::from_u8(buf.get_u8());
541        let mut reserved = [0u8; 3];
542        buf.copy_to_slice(&mut reserved);
543
544        Self {
545            app_status,
546            protocol_status,
547            reserved,
548        }
549    }
550}
551
552/// Complete end request record with header and content.
553#[derive(Debug)]
554pub(crate) struct EndRequestRec {
555    /// The FastCGI header
556    #[allow(dead_code)]
557    header: Header,
558    /// The end request data
559    pub(crate) end_request: EndRequest,
560}
561
562impl EndRequestRec {
563    /// Creates an end request record from a header and reader.
564    ///
565    /// # Arguments
566    ///
567    /// * `header` - The FastCGI header
568    /// * `reader` - The reader to read content from
569    pub(crate) async fn from_header<R: AsyncRead + Unpin>(
570        header: &Header, reader: &mut R,
571    ) -> io::Result<Self> {
572        let header = header.clone();
573        let content = header.read_content_from_stream(reader).await?;
574        Ok(Self::new_from_buf(header, content))
575    }
576
577    /// Creates an end request record from a header and buffer.
578    ///
579    /// # Arguments
580    ///
581    /// * `header` - The FastCGI header
582    /// * `buf` - The buffer containing the end request data
583    pub(crate) fn new_from_buf(header: Header, buf: BytesMut) -> Self {
584        Self {
585            header,
586            end_request: EndRequest::from(buf),
587        }
588    }
589}