gix_credentials/protocol/context/
mod.rs

1use bstr::BString;
2
3/// Indicates key or values contain errors that can't be encoded.
4#[derive(Debug, thiserror::Error)]
5#[allow(missing_docs)]
6pub enum Error {
7    #[error("{key:?}={value:?} must not contain null bytes or newlines neither in key nor in value.")]
8    Encoding { key: String, value: BString },
9}
10
11mod access {
12    use bstr::BString;
13
14    use crate::protocol::Context;
15
16    impl Context {
17        /// Clear all fields that are considered secret.
18        pub fn clear_secrets(&mut self) {
19            let Context {
20                protocol: _,
21                host: _,
22                path: _,
23                username: _,
24                password,
25                oauth_refresh_token,
26                password_expiry_utc: _,
27                url: _,
28                quit: _,
29            } = self;
30
31            *password = None;
32            *oauth_refresh_token = None;
33        }
34        /// Replace existing secrets with the word `<redacted>`.
35        pub fn redacted(mut self) -> Self {
36            let Context {
37                protocol: _,
38                host: _,
39                path: _,
40                username: _,
41                password,
42                oauth_refresh_token,
43                password_expiry_utc: _,
44                url: _,
45                quit: _,
46            } = &mut self;
47            for secret in [password, oauth_refresh_token].into_iter().flatten() {
48                *secret = "<redacted>".into();
49            }
50            self
51        }
52
53        /// Convert all relevant fields into a URL for consumption.
54        pub fn to_url(&self) -> Option<BString> {
55            use bstr::{ByteSlice, ByteVec};
56            let mut buf: BString = self.protocol.clone()?.into();
57            buf.push_str(b"://");
58            if let Some(user) = &self.username {
59                buf.push_str(user);
60                buf.push(b'@');
61            }
62            if let Some(host) = &self.host {
63                buf.push_str(host);
64            }
65            if let Some(path) = &self.path {
66                if !path.starts_with_str("/") {
67                    buf.push(b'/');
68                }
69                buf.push_str(path);
70            }
71            buf.into()
72        }
73        /// Compute a prompt to obtain the given value.
74        pub fn to_prompt(&self, field: &str) -> String {
75            match self.to_url() {
76                Some(url) => format!("{field} for {url}: "),
77                None => format!("{field}: "),
78            }
79        }
80    }
81}
82
83mod mutate {
84    use bstr::ByteSlice;
85
86    use crate::{protocol, protocol::Context};
87
88    /// In-place mutation
89    impl Context {
90        /// Destructure the url at our `url` field into parts like protocol, host, username and path and store
91        /// them in our respective fields. If `use_http_path` is set, http paths are significant even though
92        /// normally this isn't the case.
93        #[allow(clippy::result_large_err)]
94        pub fn destructure_url_in_place(&mut self, use_http_path: bool) -> Result<&mut Self, protocol::Error> {
95            let url = gix_url::parse(self.url.as_ref().ok_or(protocol::Error::UrlMissing)?.as_ref())?;
96            self.protocol = Some(url.scheme.as_str().into());
97            self.username = url.user().map(ToOwned::to_owned);
98            self.password = url.password().map(ToOwned::to_owned);
99            self.host = url.host().map(ToOwned::to_owned).map(|mut host| {
100                if let Some(port) = url.port {
101                    use std::fmt::Write;
102                    write!(host, ":{port}").expect("infallible");
103                }
104                host
105            });
106            if !matches!(url.scheme, gix_url::Scheme::Http | gix_url::Scheme::Https) || use_http_path {
107                let path = url.path.trim_with(|b| b == '/');
108                self.path = (!path.is_empty()).then(|| path.into());
109            }
110            Ok(self)
111        }
112    }
113}
114
115mod serde;
116pub use self::serde::decode;