gix_credentials/protocol/context/
serde.rs1use bstr::BStr;
2
3use crate::protocol::context::Error;
4
5mod write {
6 use bstr::{BStr, BString};
7
8 use crate::protocol::{context::serde::validate, Context};
9
10 impl Context {
11 pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> {
13 use bstr::ByteSlice;
14 fn write_key(out: &mut impl std::io::Write, key: &str, value: &BStr) -> std::io::Result<()> {
15 out.write_all(key.as_bytes())?;
16 out.write_all(b"=")?;
17 out.write_all(value)?;
18 out.write_all(b"\n")
19 }
20 let Context {
21 protocol,
22 host,
23 path,
24 username,
25 password,
26 oauth_refresh_token,
27 password_expiry_utc,
28 url,
29 quit: _,
32 } = self;
33 for (key, value) in [("url", url), ("path", path)] {
34 if let Some(value) = value {
35 validate(key, value.as_slice().into())
36 .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
37 write_key(&mut out, key, value.as_ref()).ok();
38 }
39 }
40 for (key, value) in [
41 ("protocol", protocol),
42 ("host", host),
43 ("username", username),
44 ("password", password),
45 ("oauth_refresh_token", oauth_refresh_token),
46 ] {
47 if let Some(value) = value {
48 validate(key, value.as_str().into())
49 .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
50 write_key(&mut out, key, value.as_bytes().as_bstr()).ok();
51 }
52 }
53 if let Some(value) = password_expiry_utc {
54 let key = "password_expiry_utc";
55 let value = value.to_string();
56 validate(key, value.as_str().into())
57 .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
58 write_key(&mut out, key, value.as_bytes().as_bstr()).ok();
59 }
60 Ok(())
61 }
62
63 pub fn to_bstring(&self) -> BString {
65 let mut buf = Vec::<u8>::new();
66 self.write_to(&mut buf).expect("infallible");
67 buf.into()
68 }
69 }
70}
71
72pub mod decode {
74 use bstr::{BString, ByteSlice};
75
76 use crate::protocol::{context, context::serde::validate, Context};
77
78 #[derive(Debug, thiserror::Error)]
80 #[allow(missing_docs)]
81 pub enum Error {
82 #[error("Illformed UTF-8 in value of key {key:?}: {value:?}")]
83 IllformedUtf8InValue { key: String, value: BString },
84 #[error(transparent)]
85 Encoding(#[from] context::Error),
86 #[error("Invalid format in line {line:?}, expecting key=value")]
87 Syntax { line: BString },
88 }
89
90 impl Context {
91 pub fn from_bytes(input: &[u8]) -> Result<Self, Error> {
93 let mut ctx = Context::default();
94 let Context {
95 protocol,
96 host,
97 path,
98 username,
99 password,
100 oauth_refresh_token,
101 password_expiry_utc,
102 url,
103 quit,
104 } = &mut ctx;
105 for res in input.lines().take_while(|line| !line.is_empty()).map(|line| {
106 let mut it = line.splitn(2, |b| *b == b'=');
107 match (
108 it.next().and_then(|k| k.to_str().ok()),
109 it.next().map(ByteSlice::as_bstr),
110 ) {
111 (Some(key), Some(value)) => validate(key, value)
112 .map(|_| (key, value.to_owned()))
113 .map_err(Into::into),
114 _ => Err(Error::Syntax { line: line.into() }),
115 }
116 }) {
117 let (key, value) = res?;
118 match key {
119 "protocol" | "host" | "username" | "password" | "oauth_refresh_token" => {
120 if !value.is_utf8() {
121 return Err(Error::IllformedUtf8InValue { key: key.into(), value });
122 }
123 let value = value.to_string();
124 *match key {
125 "protocol" => &mut *protocol,
126 "host" => host,
127 "username" => username,
128 "password" => password,
129 "oauth_refresh_token" => oauth_refresh_token,
130 _ => unreachable!("checked field names in match above"),
131 } = Some(value);
132 }
133 "password_expiry_utc" => {
134 *password_expiry_utc = value.to_str().ok().and_then(|value| value.parse().ok());
135 }
136 "url" => *url = Some(value),
137 "path" => *path = Some(value),
138 "quit" => {
139 *quit = gix_config_value::Boolean::try_from(value.as_ref()).ok().map(Into::into);
140 }
141 _ => {}
142 }
143 }
144 Ok(ctx)
145 }
146 }
147}
148
149fn validate(key: &str, value: &BStr) -> Result<(), Error> {
150 if key.contains('\0') || key.contains('\n') || value.contains(&0) || value.contains(&b'\n') {
151 return Err(Error::Encoding {
152 key: key.to_owned(),
153 value: value.to_owned(),
154 });
155 }
156 Ok(())
157}