Skip to main content

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