net-cat 0.3.0

Minimal hand-rolled HTTP/1.1 client over std::net::TcpStream. v0.3.0 adds chunked-transfer decoding (`Transfer-Encoding: chunked` responses now yield the correct decoded body) and a redirect follower in `fetch` (RFC 7231 §6.4: 301/302/303 downgrade non-GET/HEAD to GET and drop the body; 307/308 preserve method + body; cross-origin hops strip `Cookie` and `Authorization`; capped at `MAX_REDIRECTS = 10` hops). Optional `tls` feature still wires rustls + webpki-roots for `https://` URLs. No external HTTP crate. Sixth sub-crate of a Servo-replacement webview runtime targeting Tauri.
//! HTTP headers.

/// An ordered list of name/value pairs.  Name comparisons are
/// ASCII-case-insensitive per RFC 9110.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Headers {
    items: Vec<(String, String)>,
}

impl Headers {
    /// An empty header set.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Return a copy with `name: value` appended.
    #[must_use]
    pub fn with(self, name: impl Into<String>, value: impl Into<String>) -> Self {
        let extended: Vec<(String, String)> = self
            .items
            .into_iter()
            .chain(std::iter::once((name.into(), value.into())))
            .collect();
        Self { items: extended }
    }

    /// Look up the first value for `name` (case-insensitive).
    #[must_use]
    pub fn get(&self, name: &str) -> Option<&str> {
        self.items
            .iter()
            .find(|(k, _)| k.eq_ignore_ascii_case(name))
            .map(|(_, v)| v.as_str())
    }

    /// Return a copy with every entry whose name matches `name`
    /// (case-insensitive) removed.  Order of the remaining items
    /// is preserved.
    #[must_use]
    pub fn without(self, name: &str) -> Self {
        let kept: Vec<(String, String)> = self
            .items
            .into_iter()
            .filter(|(k, _)| !k.eq_ignore_ascii_case(name))
            .collect();
        Self { items: kept }
    }

    /// All header pairs in insertion order.
    pub fn iter(&self) -> std::slice::Iter<'_, (String, String)> {
        self.items.iter()
    }

    /// Number of headers.
    #[must_use]
    pub fn len(&self) -> usize {
        self.items.len()
    }

    /// Whether the set is empty.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.items.is_empty()
    }
}

impl<'a> IntoIterator for &'a Headers {
    type Item = &'a (String, String);
    type IntoIter = std::slice::Iter<'a, (String, String)>;
    fn into_iter(self) -> Self::IntoIter {
        self.items.iter()
    }
}