summaryrefslogtreecommitdiffstats
path: root/vendor/gix-credentials/src/protocol/context
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-credentials/src/protocol/context
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz
rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-credentials/src/protocol/context')
-rw-r--r--vendor/gix-credentials/src/protocol/context/mod.rs79
-rw-r--r--vendor/gix-credentials/src/protocol/context/serde.rs122
2 files changed, 201 insertions, 0 deletions
diff --git a/vendor/gix-credentials/src/protocol/context/mod.rs b/vendor/gix-credentials/src/protocol/context/mod.rs
new file mode 100644
index 000000000..1c578c046
--- /dev/null
+++ b/vendor/gix-credentials/src/protocol/context/mod.rs
@@ -0,0 +1,79 @@
+use bstr::BString;
+
+/// Indicates key or values contain errors that can't be encoded.
+#[derive(Debug, thiserror::Error)]
+#[allow(missing_docs)]
+pub enum Error {
+ #[error("{key:?}={value:?} must not contain null bytes or newlines neither in key nor in value.")]
+ Encoding { key: String, value: BString },
+}
+
+mod access {
+ use bstr::BString;
+
+ use crate::protocol::Context;
+
+ impl Context {
+ /// Convert all relevant fields into a URL for consumption.
+ pub fn to_url(&self) -> Option<BString> {
+ use bstr::{ByteSlice, ByteVec};
+ let mut buf: BString = self.protocol.clone()?.into();
+ buf.push_str(b"://");
+ if let Some(user) = &self.username {
+ buf.push_str(user);
+ buf.push(b'@');
+ }
+ if let Some(host) = &self.host {
+ buf.push_str(host);
+ }
+ if let Some(path) = &self.path {
+ if !path.starts_with_str("/") {
+ buf.push(b'/');
+ }
+ buf.push_str(path);
+ }
+ buf.into()
+ }
+ /// Compute a prompt to obtain the given value.
+ pub fn to_prompt(&self, field: &str) -> String {
+ match self.to_url() {
+ Some(url) => format!("{field} for {url}: "),
+ None => format!("{field}: "),
+ }
+ }
+ }
+}
+
+mod mutate {
+ use bstr::ByteSlice;
+
+ use crate::{protocol, protocol::Context};
+
+ /// In-place mutation
+ impl Context {
+ /// Destructure the url at our `url` field into parts like protocol, host, username and path and store
+ /// them in our respective fields. If `use_http_path` is set, http paths are significant even though
+ /// normally this isn't the case.
+ #[allow(clippy::result_large_err)]
+ pub fn destructure_url_in_place(&mut self, use_http_path: bool) -> Result<&mut Self, protocol::Error> {
+ let url = gix_url::parse(self.url.as_ref().ok_or(protocol::Error::UrlMissing)?.as_ref())?;
+ self.protocol = Some(url.scheme.as_str().into());
+ self.username = url.user().map(ToOwned::to_owned);
+ self.host = url.host().map(ToOwned::to_owned).map(|mut host| {
+ if let Some(port) = url.port {
+ use std::fmt::Write;
+ write!(host, ":{port}").expect("infallible");
+ }
+ host
+ });
+ if !matches!(url.scheme, gix_url::Scheme::Http | gix_url::Scheme::Https) || use_http_path {
+ let path = url.path.trim_with(|b| b == '/');
+ self.path = (!path.is_empty()).then(|| path.into());
+ }
+ Ok(self)
+ }
+ }
+}
+
+mod serde;
+pub use self::serde::decode;
diff --git a/vendor/gix-credentials/src/protocol/context/serde.rs b/vendor/gix-credentials/src/protocol/context/serde.rs
new file mode 100644
index 000000000..964195263
--- /dev/null
+++ b/vendor/gix-credentials/src/protocol/context/serde.rs
@@ -0,0 +1,122 @@
+use bstr::BStr;
+
+use crate::protocol::context::Error;
+
+mod write {
+ use bstr::{BStr, BString};
+
+ use crate::protocol::{context::serde::validate, Context};
+
+ impl Context {
+ /// Write ourselves to `out` such that [`from_bytes()`][Self::from_bytes()] can decode it losslessly.
+ pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> {
+ use bstr::ByteSlice;
+ fn write_key(out: &mut impl std::io::Write, key: &str, value: &BStr) -> std::io::Result<()> {
+ out.write_all(key.as_bytes())?;
+ out.write_all(b"=")?;
+ out.write_all(value)?;
+ out.write_all(b"\n")
+ }
+ for (key, value) in [("url", &self.url), ("path", &self.path)] {
+ if let Some(value) = value {
+ validate(key, value.as_slice().into())
+ .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
+ write_key(&mut out, key, value.as_ref())?;
+ }
+ }
+ for (key, value) in [
+ ("protocol", &self.protocol),
+ ("host", &self.host),
+ ("username", &self.username),
+ ("password", &self.password),
+ ] {
+ if let Some(value) = value {
+ validate(key, value.as_str().into())
+ .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
+ write_key(&mut out, key, value.as_bytes().as_bstr())?;
+ }
+ }
+ Ok(())
+ }
+
+ /// Like [`write_to()`][Self::write_to()], but writes infallibly into memory.
+ pub fn to_bstring(&self) -> BString {
+ let mut buf = Vec::<u8>::new();
+ self.write_to(&mut buf).expect("infallible");
+ buf.into()
+ }
+ }
+}
+
+///
+pub mod decode {
+ use std::convert::TryFrom;
+
+ use bstr::{BString, ByteSlice};
+
+ use crate::protocol::{context, context::serde::validate, Context};
+
+ /// The error returned by [`from_bytes()`][Context::from_bytes()].
+ #[derive(Debug, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error("Illformed UTF-8 in value of key {key:?}: {value:?}")]
+ IllformedUtf8InValue { key: String, value: BString },
+ #[error(transparent)]
+ Encoding(#[from] context::Error),
+ #[error("Invalid format in line {line:?}, expecting key=value")]
+ Syntax { line: BString },
+ }
+
+ impl Context {
+ /// Decode ourselves from `input` which is the format written by [`write_to()`][Self::write_to()].
+ pub fn from_bytes(input: &[u8]) -> Result<Self, Error> {
+ let mut ctx = Context::default();
+ for res in input.lines().take_while(|line| !line.is_empty()).map(|line| {
+ let mut it = line.splitn(2, |b| *b == b'=');
+ match (it.next().and_then(|k| k.to_str().ok()), it.next().map(|v| v.as_bstr())) {
+ (Some(key), Some(value)) => validate(key, value)
+ .map(|_| (key, value.to_owned()))
+ .map_err(Into::into),
+ _ => Err(Error::Syntax { line: line.into() }),
+ }
+ }) {
+ let (key, value) = res?;
+ match key {
+ "protocol" | "host" | "username" | "password" => {
+ if !value.is_utf8() {
+ return Err(Error::IllformedUtf8InValue { key: key.into(), value });
+ }
+ let value = value.to_string();
+ *match key {
+ "protocol" => &mut ctx.protocol,
+ "host" => &mut ctx.host,
+ "username" => &mut ctx.username,
+ "password" => &mut ctx.password,
+ _ => unreachable!("checked field names in match above"),
+ } = Some(value);
+ }
+ "url" => ctx.url = Some(value),
+ "path" => ctx.path = Some(value),
+ "quit" => {
+ ctx.quit = gix_config_value::Boolean::try_from(value.as_ref())
+ .ok()
+ .map(|b| b.into());
+ }
+ _ => {}
+ }
+ }
+ Ok(ctx)
+ }
+ }
+}
+
+fn validate(key: &str, value: &BStr) -> Result<(), Error> {
+ if key.contains('\0') || key.contains('\n') || value.contains(&0) || value.contains(&b'\n') {
+ return Err(Error::Encoding {
+ key: key.to_owned(),
+ value: value.to_owned(),
+ });
+ }
+ Ok(())
+}