use super::Located;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UserSpec {
raw: Located<String>,
user: IdentityComponent,
group: Option<IdentityComponent>,
}
impl UserSpec {
pub(crate) fn parse(raw: Located<String>) -> Self {
let (user, group) = split_user_group(raw.value())
.map_or_else(|| (raw.value().as_str(), None), |(user, group)| (user, Some(group)));
Self {
user: IdentityComponent::parse(user),
group: group.map(IdentityComponent::parse),
raw,
}
}
#[must_use]
pub const fn raw(&self) -> &Located<String> {
&self.raw
}
#[must_use]
pub const fn user(&self) -> &IdentityComponent {
&self.user
}
#[must_use]
pub const fn group(&self) -> Option<&IdentityComponent> {
self.group.as_ref()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IdentityComponent {
Numeric(String),
Name(String),
Expression(String),
Empty,
}
impl IdentityComponent {
fn parse(value: &str) -> Self {
if value.is_empty() {
Self::Empty
} else if value.contains("${") || value.contains("$$") {
Self::Expression(value.to_owned())
} else if value.bytes().all(|byte| byte.is_ascii_digit()) {
Self::Numeric(value.to_owned())
} else {
Self::Name(value.to_owned())
}
}
#[must_use]
pub fn raw(&self) -> &str {
match self {
Self::Numeric(value) | Self::Name(value) | Self::Expression(value) => value,
Self::Empty => "",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UserNamespaceMode {
raw: Located<String>,
kind: UserNamespaceModeKind,
}
impl UserNamespaceMode {
pub(crate) fn parse(raw: Located<String>) -> Self {
let kind = match raw.value().as_str() {
"host" | "" => UserNamespaceModeKind::Host,
value if value == "keep-id" || value.starts_with("keep-id:") => UserNamespaceModeKind::PodmanKeepId,
value if value == "auto" || value.starts_with("auto:") => UserNamespaceModeKind::PodmanAuto,
"nomap" => UserNamespaceModeKind::PodmanNoMap,
value if value.starts_with("container:") => UserNamespaceModeKind::Container,
_ => UserNamespaceModeKind::Other,
};
Self { raw, kind }
}
#[must_use]
pub const fn raw(&self) -> &Located<String> {
&self.raw
}
#[must_use]
pub const fn kind(&self) -> UserNamespaceModeKind {
self.kind
}
#[must_use]
pub const fn is_podman_specific(&self) -> bool {
matches!(
self.kind,
UserNamespaceModeKind::PodmanKeepId
| UserNamespaceModeKind::PodmanAuto
| UserNamespaceModeKind::PodmanNoMap
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UserNamespaceModeKind {
Host,
PodmanKeepId,
PodmanAuto,
PodmanNoMap,
Container,
Other,
}
fn split_user_group(value: &str) -> Option<(&str, &str)> {
let bytes = value.as_bytes();
let mut interpolation_depth = 0_usize;
let mut index = 0_usize;
while index < bytes.len() {
if bytes[index] == b'$' && bytes.get(index + 1) == Some(&b'{') {
interpolation_depth += 1;
index += 2;
continue;
}
if bytes[index] == b'}' && interpolation_depth > 0 {
interpolation_depth -= 1;
} else if bytes[index] == b':' && interpolation_depth == 0 {
return Some((&value[..index], &value[index + 1..]));
}
index += 1;
}
None
}
#[cfg(test)]
mod tests {
use super::{IdentityComponent, UserSpec};
use crate::model::Located;
use crate::source::{SourceId, SourceSpan};
#[test]
fn does_not_split_interpolation_default_operators() -> Result<(), &'static str> {
let value = "${UID:-1000}:${GID:-1000}";
let span = SourceSpan::new(SourceId::new(1), 0, value.len()).ok_or("valid test span expected")?;
let parsed = UserSpec::parse(Located::new(value.to_owned(), span));
assert_eq!(parsed.user(), &IdentityComponent::Expression("${UID:-1000}".to_owned()));
assert_eq!(
parsed.group(),
Some(&IdentityComponent::Expression("${GID:-1000}".to_owned()))
);
Ok(())
}
}