summaryrefslogtreecommitdiffstats
path: root/vendor/gix-credentials/src/protocol
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:35 +0000
commit7e5d7eea9c580ef4b41a765bde624af431942b96 (patch)
tree2c0d9ca12878fc4525650aa4e54d77a81a07cc09 /vendor/gix-credentials/src/protocol
parentAdding debian version 1.70.0+dfsg1-9. (diff)
downloadrustc-7e5d7eea9c580ef4b41a765bde624af431942b96.tar.xz
rustc-7e5d7eea9c580ef4b41a765bde624af431942b96.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')
-rw-r--r--vendor/gix-credentials/src/protocol/context/mod.rs79
-rw-r--r--vendor/gix-credentials/src/protocol/context/serde.rs122
-rw-r--r--vendor/gix-credentials/src/protocol/mod.rs86
3 files changed, 287 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(())
+}
diff --git a/vendor/gix-credentials/src/protocol/mod.rs b/vendor/gix-credentials/src/protocol/mod.rs
new file mode 100644
index 000000000..ec168ffb3
--- /dev/null
+++ b/vendor/gix-credentials/src/protocol/mod.rs
@@ -0,0 +1,86 @@
+use bstr::BString;
+
+use crate::helper;
+
+/// The outcome of the credentials top-level functions to obtain a complete identity.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Outcome {
+ /// The identity provide by the helper.
+ pub identity: gix_sec::identity::Account,
+ /// A handle to the action to perform next in another call to [`helper::invoke()`][crate::helper::invoke()].
+ pub next: helper::NextAction,
+}
+
+/// The Result type used in credentials top-level functions to obtain a complete identity.
+pub type Result = std::result::Result<Option<Outcome>, Error>;
+
+/// The error returned top-level credential functions.
+#[derive(Debug, thiserror::Error)]
+#[allow(missing_docs)]
+pub enum Error {
+ #[error(transparent)]
+ UrlParse(#[from] gix_url::parse::Error),
+ #[error("The 'url' field must be set when performing a 'get/fill' action")]
+ UrlMissing,
+ #[error(transparent)]
+ ContextDecode(#[from] context::decode::Error),
+ #[error(transparent)]
+ InvokeHelper(#[from] helper::Error),
+ #[error("Could not obtain identity for context: {}", { let mut buf = Vec::<u8>::new(); context.write_to(&mut buf).ok(); String::from_utf8_lossy(&buf).into_owned() })]
+ IdentityMissing { context: Context },
+ #[error("The handler asked to stop trying to obtain credentials")]
+ Quit,
+ #[error("Couldn't obtain {prompt}")]
+ Prompt { prompt: String, source: gix_prompt::Error },
+}
+
+/// Additional context to be passed to the credentials helper.
+#[derive(Debug, Default, Clone, Eq, PartialEq)]
+pub struct Context {
+ /// The protocol over which the credential will be used (e.g., https).
+ pub protocol: Option<String>,
+ /// The remote hostname for a network credential. This includes the port number if one was specified (e.g., "example.com:8088").
+ pub host: Option<String>,
+ /// The path with which the credential will be used. E.g., for accessing a remote https repository, this will be the repository’s path on the server.
+ /// It can also be a path on the file system.
+ pub path: Option<BString>,
+ /// The credential’s username, if we already have one (e.g., from a URL, the configuration, the user, or from a previously run helper).
+ pub username: Option<String>,
+ /// The credential’s password, if we are asking it to be stored.
+ pub password: Option<String>,
+ /// When this special attribute is read by git credential, the value is parsed as a URL and treated as if its constituent
+ /// parts were read (e.g., url=<https://example.com> would behave as if
+ /// protocol=https and host=example.com had been provided). This can help callers avoid parsing URLs themselves.
+ pub url: Option<BString>,
+ /// If true, the caller should stop asking for credentials immediately without calling more credential helpers in the chain.
+ pub quit: Option<bool>,
+}
+
+/// Convert the outcome of a helper invocation to a helper result, assuring that the identity is complete in the process.
+#[allow(clippy::result_large_err)]
+pub fn helper_outcome_to_result(outcome: Option<helper::Outcome>, action: helper::Action) -> Result {
+ fn redact(mut ctx: Context) -> Context {
+ if let Some(pw) = ctx.password.as_mut() {
+ *pw = "<redacted>".into()
+ }
+ ctx
+ }
+ match (action, outcome) {
+ (helper::Action::Get(ctx), None) => Err(Error::IdentityMissing { context: redact(ctx) }),
+ (helper::Action::Get(ctx), Some(mut outcome)) => match outcome.consume_identity() {
+ Some(identity) => Ok(Some(Outcome {
+ identity,
+ next: outcome.next,
+ })),
+ None => Err(if outcome.quit {
+ Error::Quit
+ } else {
+ Error::IdentityMissing { context: redact(ctx) }
+ }),
+ },
+ (helper::Action::Store(_) | helper::Action::Erase(_), _ignore) => Ok(None),
+ }
+}
+
+///
+pub mod context;