162 lines · 6.6 KB · Rust
RawDownload
use bstr::BString; /// Indicates key or values contain errors that can't be encoded.#[derive(Debug, thiserror::Error)]#[allow(missing_docs)]pub enum Error {    #[error("{key:?}={value:?} must not contain null bytes or newlines neither in key nor in value.")]    Encoding { key: String, value: BString },} mod serde {    use crate::protocol::context::Error;    use bstr::BStr;     mod write {        use crate::protocol::context::serde::validate;        use crate::protocol::Context;        use bstr::{BStr, BString};         impl Context {            /// Convert all relevant fields into a URL for consumption.            pub fn to_url(&self) -> Option<BString> {                use bstr::{ByteSlice, ByteVec};                let mut buf: BString = self.protocol.clone()?.into();                buf.push_str(b"://");                if let Some(user) = &self.username {                    buf.push_str(user);                    buf.push(b'@');                }                if let Some(host) = &self.host {                    buf.push_str(host);                }                if let Some(path) = &self.path {                    if !path.starts_with_str("/") {                        buf.push(b'/');                    }                    buf.push_str(path);                }                buf.into()            }            /// Compute a prompt to obtain the given value.            pub fn to_prompt(&self, field: &str) -> String {                match self.to_url() {                    Some(url) => format!("{field} for {url}: "),                    None => format!("{field}: "),                }            }             /// Write ourselves to `out` such that [`from_bytes()`][Self::from_bytes()] can decode it losslessly.            pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> {                use bstr::ByteSlice;                fn write_key(out: &mut impl std::io::Write, key: &str, value: &BStr) -> std::io::Result<()> {                    out.write_all(key.as_bytes())?;                    out.write_all(b"=")?;                    out.write_all(value)?;                    out.write_all(b"\n")                }                for (key, value) in [("url", &self.url), ("path", &self.path)] {                    if let Some(value) = value {                        validate(key, value.as_slice().into())                            .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;                        write_key(&mut out, key, value.as_ref())?;                    }                }                for (key, value) in [                    ("protocol", &self.protocol),                    ("host", &self.host),                    ("username", &self.username),                    ("password", &self.password),                ] {                    if let Some(value) = value {                        validate(key, value.as_str().into())                            .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;                        write_key(&mut out, key, value.as_bytes().as_bstr())?;                    }                }                if let Some(quit) = self.quit {                    write_key(                        &mut out,                        "quit",                        quit.then(|| &b"true"[..]).unwrap_or(b"false").as_bstr(),                    )?;                }                Ok(())            }        }    }     ///    pub mod decode {        use crate::protocol::context;        use crate::protocol::context::serde::validate;        use crate::protocol::Context;        use bstr::{BString, ByteSlice};        use std::convert::TryFrom;         /// The error returned by [`from_bytes()`][Context::from_bytes()].        #[derive(Debug, thiserror::Error)]        #[allow(missing_docs)]        pub enum Error {            #[error("Illformed UTF-8 in value of key {key:?}: {value:?}")]            IllformedUtf8InValue { key: String, value: BString },            #[error(transparent)]            Encoding(#[from] context::Error),            #[error("Invalid format in line {line:?}, expecting key=value")]            Syntax { line: BString },        }         impl Context {            /// Decode ourselves from `input` which is the format written by [`write_to()`][Self::write_to()].            pub fn from_bytes(input: &[u8]) -> Result<Self, Error> {                let mut ctx = Context::default();                for res in input.lines().take_while(|line| !line.is_empty()).map(|line| {                    let mut it = line.splitn(2, |b| *b == b'=');                    match (it.next().and_then(|k| k.to_str().ok()), it.next().map(|v| v.as_bstr())) {                        (Some(key), Some(value)) => validate(key, value)                            .map(|_| (key, value.to_owned()))                            .map_err(Into::into),                        _ => Err(Error::Syntax { line: line.into() }),                    }                }) {                    let (key, value) = res?;                    match key {                        "protocol" | "host" | "username" | "password" => {                            if !value.is_utf8() {                                return Err(Error::IllformedUtf8InValue { key: key.into(), value });                            }                            let value = value.to_string();                            *match key {                                "protocol" => &mut ctx.protocol,                                "host" => &mut ctx.host,                                "username" => &mut ctx.username,                                "password" => &mut ctx.password,                                _ => unreachable!("checked field names in match above"),                            } = Some(value);                        }                        "url" => ctx.url = Some(value),                        "path" => ctx.path = Some(value),                        "quit" => {                            ctx.quit = git_config_value::Boolean::try_from(value.as_ref())                                .ok()                                .map(|b| b.into());                        }                        _ => {}                    }                }                Ok(ctx)            }        }    }     fn validate(key: &str, value: &BStr) -> Result<(), Error> {        if key.contains('\0') || key.contains('\n') || value.contains(&0) || value.contains(&b'\n') {            return Err(Error::Encoding {                key: key.to_owned(),                value: value.to_owned(),            });        }        Ok(())    }}pub use self::serde::decode;