gix_credentials/protocol/context/
mod.rs1use bstr::BString;
2
3#[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 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 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 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 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 impl Context {
90 #[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;