use std::os::unix::ffi::{OsStrExt as _, OsStringExt as _};
pub const VERSION: u32 = {
const fn unwrap(res: &Result<u32, std::num::ParseIntError>) -> u32 {
match res {
Ok(t) => *t,
Err(_) => panic!("failed to parse cargo version"),
}
}
let major = env!("CARGO_PKG_VERSION_MAJOR");
let minor = env!("CARGO_PKG_VERSION_MINOR");
let patch = env!("CARGO_PKG_VERSION_PATCH");
unwrap(&u32::from_str_radix(major, 10)) * 1_000_000
+ unwrap(&u32::from_str_radix(minor, 10)) * 1_000_000
+ unwrap(&u32::from_str_radix(patch, 10)) * 1_000_000
};
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct Request {
tty: Option<String>,
environment: Option<Environment>,
action: Action,
#[serde(default, skip_serializing_if = "Option::is_none")]
session_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
purpose: Option<String>,
}
impl Request {
pub fn new(environment: Environment, action: Action) -> Self {
Self {
tty: None,
environment: Some(environment),
action,
session_id: None,
purpose: None,
}
}
pub fn new_with_session(
environment: Environment,
action: Action,
session_id: String,
purpose: Option<String>,
) -> Self {
Self {
tty: None,
environment: Some(environment),
action,
session_id: Some(session_id),
purpose,
}
}
pub fn into_parts(
self,
) -> (Action, Environment, Option<String>, Option<String>) {
(
self.action,
self.environment.unwrap_or_else(|| Environment {
tty: self.tty.map(|tty| SerializableOsString(tty.into())),
env_vars: vec![],
}),
self.session_id,
self.purpose,
)
}
}
pub const ENVIRONMENT_VARIABLES: &[&str] = &[
"TERM",
"DISPLAY",
"XAUTHORITY",
"XMODIFIERS",
"WAYLAND_DISPLAY",
"XDG_SESSION_TYPE",
"QT_QPA_PLATFORM",
"GTK_IM_MODULE",
"DBUS_SESSION_BUS_ADDRESS",
"QT_IM_MODULE",
"PINENTRY_USER_DATA",
"PINENTRY_GEOM_HINT",
];
pub static ENVIRONMENT_VARIABLES_OS: std::sync::LazyLock<
Vec<std::ffi::OsString>,
> = std::sync::LazyLock::new(|| {
ENVIRONMENT_VARIABLES
.iter()
.map(std::ffi::OsString::from)
.collect()
});
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
struct SerializableOsString(std::ffi::OsString);
impl serde::Serialize for SerializableOsString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&crate::base64::encode(self.0.as_bytes()))
}
}
impl<'de> serde::Deserialize<'de> for SerializableOsString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = SerializableOsString;
fn expecting(
&self,
formatter: &mut std::fmt::Formatter,
) -> std::fmt::Result {
formatter.write_str("base64 encoded os string")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(SerializableOsString(std::ffi::OsString::from_vec(
crate::base64::decode(s).map_err(|_| {
E::invalid_value(serde::de::Unexpected::Str(s), &self)
})?,
)))
}
}
deserializer.deserialize_str(Visitor)
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Default, Clone)]
pub struct Environment {
tty: Option<SerializableOsString>,
env_vars: Vec<(SerializableOsString, SerializableOsString)>,
}
impl Environment {
pub fn new(
tty: Option<std::ffi::OsString>,
env_vars: Vec<(std::ffi::OsString, std::ffi::OsString)>,
) -> Self {
Self {
tty: tty.map(SerializableOsString),
env_vars: env_vars
.into_iter()
.map(|(k, v)| {
(SerializableOsString(k), SerializableOsString(v))
})
.collect(),
}
}
pub fn tty(&self) -> Option<&std::ffi::OsStr> {
self.tty.as_ref().map(|tty| tty.0.as_os_str())
}
pub fn env_vars(
&self,
) -> std::collections::HashMap<std::ffi::OsString, std::ffi::OsString>
{
self.env_vars
.iter()
.map(|(var, val)| (var.0.clone(), val.0.clone()))
.filter(|(var, _)| (*ENVIRONMENT_VARIABLES_OS).contains(var))
.collect()
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(tag = "type")]
pub enum Action {
Login,
Register,
Unlock,
CheckLock,
Lock,
Sync,
Decrypt {
cipherstring: String,
entry_key: Option<String>,
org_id: Option<String>,
},
Encrypt {
plaintext: String,
org_id: Option<String>,
},
ClipboardStore {
text: String,
},
Quit,
Version,
TouchIdEnroll,
TouchIdDisable,
TouchIdStatus,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(tag = "type")]
pub enum Response {
Ack,
Error {
error: String,
},
Decrypt {
plaintext: String,
},
Encrypt {
cipherstring: String,
},
Version {
version: u32,
},
TouchIdStatus {
enrolled: bool,
gate: String,
keychain_label: Option<String>,
},
}