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 for (key, value) in [("url", &self.url), ("path", &self.path)] {
21 if let Some(value) = value {
22 validate(key, value.as_slice().into())
23 .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
24 write_key(&mut out, key, value.as_ref()).ok();
25 }
26 }
27 for (key, value) in [
28 ("protocol", &self.protocol),
29 ("host", &self.host),
30 ("username", &self.username),
31 ("password", &self.password),
32 ] {
33 if let Some(value) = value {
34 validate(key, value.as_str().into())
35 .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
36 write_key(&mut out, key, value.as_bytes().as_bstr()).ok();
37 }
38 }
39 Ok(())
40 }
41
42 pub fn to_bstring(&self) -> BString {
44 let mut buf = Vec::<u8>::new();
45 self.write_to(&mut buf).expect("infallible");
46 buf.into()
47 }
48 }
49}
50
51pub mod decode {
53 use bstr::{BString, ByteSlice};
54
55 use crate::protocol::{context, context::serde::validate, Context};
56
57 #[derive(Debug, thiserror::Error)]
59 #[allow(missing_docs)]
60 pub enum Error {
61 #[error("Illformed UTF-8 in value of key {key:?}: {value:?}")]
62 IllformedUtf8InValue { key: String, value: BString },
63 #[error(transparent)]
64 Encoding(#[from] context::Error),
65 #[error("Invalid format in line {line:?}, expecting key=value")]
66 Syntax { line: BString },
67 }
68
69 impl Context {
70 pub fn from_bytes(input: &[u8]) -> Result<Self, Error> {
72 let mut ctx = Context::default();
73 for res in input.lines().take_while(|line| !line.is_empty()).map(|line| {
74 let mut it = line.splitn(2, |b| *b == b'=');
75 match (
76 it.next().and_then(|k| k.to_str().ok()),
77 it.next().map(ByteSlice::as_bstr),
78 ) {
79 (Some(key), Some(value)) => validate(key, value)
80 .map(|_| (key, value.to_owned()))
81 .map_err(Into::into),
82 _ => Err(Error::Syntax { line: line.into() }),
83 }
84 }) {
85 let (key, value) = res?;
86 match key {
87 "protocol" | "host" | "username" | "password" => {
88 if !value.is_utf8() {
89 return Err(Error::IllformedUtf8InValue { key: key.into(), value });
90 }
91 let value = value.to_string();
92 *match key {
93 "protocol" => &mut ctx.protocol,
94 "host" => &mut ctx.host,
95 "username" => &mut ctx.username,
96 "password" => &mut ctx.password,
97 _ => unreachable!("checked field names in match above"),
98 } = Some(value);
99 }
100 "url" => ctx.url = Some(value),
101 "path" => ctx.path = Some(value),
102 "quit" => {
103 ctx.quit = gix_config_value::Boolean::try_from(value.as_ref()).ok().map(Into::into);
104 }
105 _ => {}
106 }
107 }
108 Ok(ctx)
109 }
110 }
111}
112
113fn validate(key: &str, value: &BStr) -> Result<(), Error> {
114 if key.contains('\0') || key.contains('\n') || value.contains(&0) || value.contains(&b'\n') {
115 return Err(Error::Encoding {
116 key: key.to_owned(),
117 value: value.to_owned(),
118 });
119 }
120 Ok(())
121}