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 if self.url.is_none() {
96 self.url = Some(self.to_url().ok_or(protocol::Error::UrlMissing)?);
97 }
98
99 let url = gix_url::parse(self.url.as_ref().expect("URL is present after check above").as_ref())?;
100 self.protocol = Some(url.scheme.as_str().into());
101 self.username = url.user().map(ToOwned::to_owned);
102 self.password = url.password().map(ToOwned::to_owned);
103 self.host = url.host().map(ToOwned::to_owned).map(|mut host| {
104 if let Some(port) = url.port {
105 use std::fmt::Write;
106 write!(host, ":{port}").expect("infallible");
107 }
108 host
109 });
110 if !matches!(url.scheme, gix_url::Scheme::Http | gix_url::Scheme::Https) || use_http_path {
111 let path = url.path.trim_with(|b| b == '/');
112 self.path = (!path.is_empty()).then(|| path.into());
113 }
114 Ok(self)
115 }
116 }
117}
118
119mod serde;
120pub use self::serde::decode;