Skip to main content

cegla/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! A low-level parsing library for CGI (and CGI-like protocols)
4
5use std::{
6  collections::HashMap,
7  pin::Pin,
8  task::{Context, Poll},
9};
10
11use bytes::Bytes;
12use futures_util::Stream;
13use hashlink::LinkedHashMap;
14use http_body::Body;
15use tokio_util::io::ReaderStream;
16
17#[cfg(feature = "client")]
18pub mod client;
19#[cfg(feature = "server")]
20pub mod server;
21
22/// A CGI-like incoming body
23#[derive(Debug)]
24pub struct CgiIncoming<R> {
25  pub(crate) inner: Pin<Box<ReaderStream<R>>>,
26}
27
28#[allow(unused)]
29impl<R> CgiIncoming<R>
30where
31  R: tokio::io::AsyncRead,
32{
33  /// Creates a new instance of `CgiIncoming`
34  pub(crate) fn new(inner: R) -> Self {
35    Self {
36      inner: Box::pin(ReaderStream::new(inner)),
37    }
38  }
39}
40
41impl<R> Body for CgiIncoming<R>
42where
43  R: tokio::io::AsyncRead,
44{
45  type Data = Bytes;
46  type Error = std::io::Error;
47
48  fn poll_frame(
49    mut self: Pin<&mut Self>,
50    cx: &mut Context<'_>,
51  ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
52    match Pin::new(&mut self.inner).poll_next(cx) {
53      Poll::Ready(Some(Ok(data))) => Poll::Ready(Some(Ok(http_body::Frame::data(data)))),
54      Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err))),
55      Poll::Ready(None) => Poll::Ready(None),
56      Poll::Pending => Poll::Pending,
57    }
58  }
59}
60
61/// A map of CGI environment variables.
62#[derive(Debug, Clone, PartialEq, Eq, Hash)]
63pub struct CgiEnvironment {
64  pub(crate) inner: hashlink::LinkedHashMap<String, String>,
65}
66
67impl CgiEnvironment {
68  /// Obtains the value of the specified environment variable.
69  pub fn get(&self, key: &str) -> Option<&str> {
70    let key = key.to_uppercase();
71    self.inner.get(&key).map(|value| value.as_str())
72  }
73
74  /// Determines whether the specified environment variable exists.
75  pub fn contains_key(&self, key: &str) -> bool {
76    let key = key.to_uppercase();
77    self.inner.contains_key(&key)
78  }
79
80  /// Obtains an iterator over the environment variables.
81  pub fn iter<'a>(&'a self) -> hashlink::linked_hash_map::Iter<'a, String, String> {
82    self.inner.iter()
83  }
84
85  /// Returns the total number of environment variables.
86  pub fn len(&self) -> usize {
87    self.inner.len()
88  }
89
90  /// Checks whether the environment is empty.
91  pub fn is_empty(&self) -> bool {
92    self.inner.is_empty()
93  }
94}
95
96impl std::ops::Index<&str> for CgiEnvironment {
97  type Output = str;
98
99  fn index(&self, key: &str) -> &Self::Output {
100    let key = key.to_uppercase();
101    self
102      .inner
103      .get(&key)
104      .unwrap_or_else(|| panic!("Missing environment variable: {}", key))
105      .as_str()
106  }
107}
108
109impl IntoIterator for CgiEnvironment {
110  type Item = (String, String);
111  type IntoIter = hashlink::linked_hash_map::IntoIter<String, String>;
112
113  fn into_iter(self) -> Self::IntoIter {
114    self.inner.into_iter()
115  }
116}
117
118impl<'a> IntoIterator for &'a CgiEnvironment {
119  type Item = (&'a String, &'a String);
120  type IntoIter = hashlink::linked_hash_map::Iter<'a, String, String>;
121
122  fn into_iter(self) -> Self::IntoIter {
123    self.inner.iter()
124  }
125}
126
127impl From<LinkedHashMap<String, String>> for CgiEnvironment {
128  fn from(map: LinkedHashMap<String, String>) -> Self {
129    Self { inner: map }
130  }
131}
132
133impl From<HashMap<String, String>> for CgiEnvironment {
134  fn from(map: HashMap<String, String>) -> Self {
135    Self {
136      inner: map.into_iter().collect(),
137    }
138  }
139}
140
141impl FromIterator<(String, String)> for CgiEnvironment {
142  fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
143    Self {
144      inner: iter.into_iter().collect(),
145    }
146  }
147}