#![deny(warnings, rust_2018_idioms)]
use bytes::{BufMut, BytesMut};
use std::{io, mem};
use tokio_codec::{Decoder, Encoder};
const NUL: u8 = b'\0';
/// The maximum size in bytes of a single header name or value. This limit is far greater than the
/// 4k-8k that is enforced by most web servers.
const MAX_HEADER_STRING_BYTES: usize = 32 * 1024;
/// The maximum size in bytes for all header content. This limit is far greater than the 4k-8k that
/// is enforced by most web servers.
const MAX_HEADER_BYTES: usize = 256 * 1024;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SCGIRequest {
/// The first Vec contains the headers. The second Vec optionally contains raw byte data from
/// the request body.
Request(Vec<(String, String)>, BytesMut),
/// Additional body fragment(s) to be used for streaming request data.
BodyFragment(BytesMut),
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum CodecState {
/// Getting the initial netstring size.
/// => HeaderKey when ':' is encountered and header_size > 0.
/// => ContentSeparator when ':' is encountered and header_size == 0.
HeaderSize,
/// Getting a header key.
/// => HeaderValue when NUL is encountered.
HeaderKey,
/// Getting a header value.
/// => HeaderKey when NUL is encountered and remaining_header_size > 0.
/// => ContentSeparator when NUL is encountered and remaining_header_size == 0.
HeaderValue,
/// Getting the ',' separating headers from content.
/// => Content when ',' is encountered.
ContentSeparator,
/// Forwarding any payload content, may match CONTENT_SIZE header.
Content,
}
/// A `Codec` implementation that creates and parses SCGI requests.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SCGICodec {
/// Decoder state. See `CodecState` for transition info.
decoder_state: CodecState,
/// The amount of unconsumed header remaining. There should be a ',' at this index.
header_remaining: usize,
/// The accumulated header_key, assigned when exiting HeaderKey state and cleared/consumed when
/// leaving HeaderValue state
header_key: String,
/// The accumulated headers, populated when leaving HeaderValue states and forwarded to caller
/// when entering Content state from last HeaderValue state. Intentionally using a `Vec` to
/// preserve ordering.
headers: Vec<(String, String)>,
/// Pointer to index where searches should begin for a character in the provided buffer. Must be
/// reset to 0 after consuming from the buffer.
next_search_index: usize,
}
macro_rules! io_err {
($($arg:tt)*) => (Err(io::Error::new(io::ErrorKind::InvalidData, format!($($arg)+))))
}
impl SCGICodec {
/// Returns a `SCGIServerCodec` for accepting and parsing SCGI-format requests.
pub fn new() -> SCGICodec {
SCGICodec {
decoder_state: CodecState::HeaderSize,
header_remaining: 0,
header_key: String::new(),
headers: Vec::new(),
next_search_index: 0,
}
}
/// Loops and consumes all available headers in the buffer, returning a `SCGIRequest::Headers`
/// result if complete headers were available, or `None` if the end of the headers wasn't yet
/// reachable in the buffer.
fn consume_headers(&mut self, buf: &mut BytesMut) -> Result