tokio_scgi/
client.rs

1#![deny(warnings)]
2
3use bytes::{BufMut, BytesMut};
4use std::io;
5use tokio_util::codec::{Decoder, Encoder};
6
7const NUL: u8 = b'\0';
8
9/// A parsed SCGI request header with key/value header data, and/or bytes from the raw request body.
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub enum SCGIRequest {
12    /// The Vec contains the headers. The BytesMut optionally contains raw byte data from
13    /// the request body, which may be followed by additional `BodyFragment`s in later calls.
14    /// The `Content-Length` header, required by SCGI, can be used to detect whether to wait for
15    /// additional `BodyFragment`s.
16    Request(Vec<(String, String)>, BytesMut),
17
18    /// Additional body fragment(s), used for streaming fragmented request body data. These should
19    /// only be relevant in cases where the leading `Request` value doesn't contain all of the body.
20    BodyFragment(BytesMut),
21}
22
23/// A `Codec` implementation that creates SCGI requests for SCGI clients like web servers.
24/// The Encoder accepts `SCGIRequest` objects containing header/body request data and encodes them for
25/// sending to an SCGI server. The Decoder passes through the raw response returned by the SCGI server.
26#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct SCGICodec {}
28
29impl SCGICodec {
30    /// Returns a client `SCGICodec` for creating SCGI-format requests for use by SCGI clients
31    /// like web servers.
32    pub fn new() -> SCGICodec {
33        SCGICodec {}
34    }
35}
36
37/// Passes through any response data as-is. To be handled by the requesting client.
38impl Decoder for SCGICodec {
39    type Item = BytesMut;
40    type Error = io::Error;
41
42    fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<BytesMut>, io::Error> {
43        // Forward content (HTTP response, typically?) as-is
44        Ok(Some(buf.split_to(buf.len())))
45    }
46}
47
48/// Creates and produces SCGI requests. Invoke once with `Request`, followed by zero or more calls
49/// with `BodyFragment`.
50impl Encoder<SCGIRequest> for SCGICodec {
51    type Error = io::Error;
52
53    fn encode(&mut self, data: SCGIRequest, buf: &mut BytesMut) -> Result<(), io::Error> {
54        match data {
55            SCGIRequest::Request(env_map, body) => {
56                // Calculate size needed for header netstring
57                let mut sum_header_size: usize = 0;
58                for (k, v) in &env_map {
59                    // While we're iterating over the keys/values, do some basic validation per the
60                    // SCGI protocol spec.
61                    if k.len() == 0 {
62                        return Err(io::Error::new(
63                            io::ErrorKind::InvalidInput,
64                            format!("Keys in request header cannot be empty"),
65                        ));
66                    }
67                    if k.as_bytes().contains(&NUL) || v.as_bytes().contains(&NUL) {
68                        return Err(io::Error::new(
69                            io::ErrorKind::InvalidInput,
70                            format!("Keys/values in request header cannot contain NUL character"),
71                        ));
72                    }
73                    // Include 2 x NUL in size:
74                    sum_header_size += k.len() + 1/*NUL*/ + v.len() + 1/*NUL*/;
75                }
76                let netstring_size_str = sum_header_size.to_string();
77                // Include ':' and ',' in buffer, not included in netstring size:
78                buf.reserve(
79                    netstring_size_str.len() + 1/*:*/ + sum_header_size + 1/*,*/ + body.len(),
80                );
81
82                // Insert the header content into the reserved buffer.
83                buf.put_slice(netstring_size_str.as_bytes());
84                buf.put_u8(b':');
85                for (k, v) in &env_map {
86                    buf.put(k.as_bytes());
87                    buf.put_u8(NUL);
88                    buf.put(v.as_bytes());
89                    buf.put_u8(NUL);
90                }
91                buf.put_u8(b',');
92
93                // Add any body content after the header
94                buf.put(body);
95            }
96            SCGIRequest::BodyFragment(fragment) => {
97                // Forward content as-is
98                buf.reserve(fragment.len());
99                buf.put(fragment);
100            }
101        }
102        Ok(())
103    }
104}