An attempt at safer message passing.

This commit is contained in:
Jeremy Cantrell 2022-10-16 04:33:08 -05:00 committed by Arijit Basu
parent 895d55ca23
commit 2b5755aa8a
7 changed files with 159 additions and 68 deletions

View File

@ -15,6 +15,7 @@ pub use crate::msg::in_::ExternalMsg;
pub use crate::msg::in_::InternalMsg;
pub use crate::msg::in_::MsgIn;
pub use crate::msg::out::MsgOut;
use crate::newlines::unescape_string;
pub use crate::node::Node;
pub use crate::node::ResolvedNode;
pub use crate::pipe::Pipe;
@ -174,6 +175,7 @@ pub struct App {
pub mode: Mode,
pub layout: Layout,
pub input: InputBuffer,
pub input_unescaped: String,
pub pid: u32,
pub session_path: String,
pub pipe: Pipe,
@ -308,6 +310,7 @@ impl App {
mode,
layout,
input,
input_unescaped: Default::default(),
pid,
session_path: session_path.clone(),
pipe: Pipe::from_session_path(&session_path)?,
@ -554,6 +557,14 @@ impl App {
}
});
self.input_unescaped = unescape_string(
&self.input
.buffer
.as_ref()
.map(|i| i.value().to_string())
.unwrap_or_default(),
)?;
for msg in msgs {
self = self.enqueue(Task::new(MsgIn::External(msg), Some(key)));
}

View File

@ -1156,7 +1156,7 @@ xplr.config.modes.builtin.default = {
{
BashExecSilently = [===[
NAME=$(basename "${XPLR_FOCUS_PATH:?}")
echo SetInputBuffer: "'${NAME:?}'" >> "${XPLR_PIPE_MSG_IN:?}"
printf "SetInputBuffer: %s\0" "${NAME:?}" >> "${XPLR_PIPE_MSG_IN:?}"
]===],
},
},
@ -1169,7 +1169,7 @@ xplr.config.modes.builtin.default = {
{
BashExecSilently = [===[
NAME=$(basename "${XPLR_FOCUS_PATH:?}")
echo SetInputBuffer: "'${NAME:?}'" >> "${XPLR_PIPE_MSG_IN:?}"
printf "SetInputBuffer: %s\0" "${NAME:?}" >> "${XPLR_PIPE_MSG_IN:?}"
]===],
},
},
@ -1205,7 +1205,7 @@ xplr.config.modes.builtin.default = {
messages = {
{
BashExecSilently = [===[
echo ChangeDirectory: "'${HOME:?}'" >> "${XPLR_PIPE_MSG_IN:?}"
printf "ChangeDirectory: %s\0" "${HOME:?}" >> "${XPLR_PIPE_MSG_IN:?}"
]===],
},
},
@ -1364,9 +1364,9 @@ xplr.config.modes.builtin.go_to_path = {
{
BashExecSilently = [===[
if [ -d "$XPLR_INPUT_BUFFER" ]; then
echo ChangeDirectory: "'$XPLR_INPUT_BUFFER'" >> "${XPLR_PIPE_MSG_IN:?}"
printf "ChangeDirectory: %s\0" "$XPLR_INPUT_BUFFER" >> "${XPLR_PIPE_MSG_IN:?}"
elif [ -e "$XPLR_INPUT_BUFFER" ]; then
echo FocusPath: "'$XPLR_INPUT_BUFFER'" >> "${XPLR_PIPE_MSG_IN:?}"
printf "FocusPath: %s\0" "$XPLR_INPUT_BUFFER" >> "${XPLR_PIPE_MSG_IN:?}"
fi
]===],
},
@ -1400,15 +1400,15 @@ xplr.config.modes.builtin.selection_ops = {
messages = {
{
BashExec = [===[
(while IFS= read -r line; do
if cp -vr -- "${line:?}" ./; then
echo LogSuccess: "'$line copied to $PWD'" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo LogError: "'Failed to copy $line to $PWD'" >> "${XPLR_PIPE_MSG_IN:?}"
fi
(while IFS= read -r -d '' line; do
if cp -vr -- "${line:?}" ./; then
printf "LogSuccess: %s\0" "$line copied to $PWD" >> "${XPLR_PIPE_MSG_IN:?}"
else
printf "LogError: %s\0" "Failed to copy $line to $PWD" >> "${XPLR_PIPE_MSG_IN:?}"
fi
done < "${XPLR_PIPE_SELECTION_OUT:?}")
echo ExplorePwdAsync >> "${XPLR_PIPE_MSG_IN:?}"
echo ClearSelection >> "${XPLR_PIPE_MSG_IN:?}"
printf "ExplorePwdAsync\0" >> "${XPLR_PIPE_MSG_IN:?}"
printf "ClearSelection\0" >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
]===],
},
@ -1420,14 +1420,14 @@ xplr.config.modes.builtin.selection_ops = {
messages = {
{
BashExec = [===[
(while IFS= read -r line; do
if mv -v -- "${line:?}" ./; then
echo LogSuccess: "'$line moved to $PWD'" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo LogError: "'Failed to move $line to $PWD'" >> "${XPLR_PIPE_MSG_IN:?}"
fi
(while IFS= read -r -d '' line; do
if mv -v -- "${line:?}" ./; then
printf "LogSuccess: %s\0" "$line moved to $PWD" >> "${XPLR_PIPE_MSG_IN:?}"
else
printf "LogError: %s\0" "Failed to move $line to $PWD" >> "${XPLR_PIPE_MSG_IN:?}"
fi
done < "${XPLR_PIPE_SELECTION_OUT:?}")
echo ExplorePwdAsync >> "${XPLR_PIPE_MSG_IN:?}"
printf "ExplorePwdAsync\0" >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
]===],
},
@ -1452,12 +1452,12 @@ xplr.config.modes.builtin.selection_ops = {
elif command -v open; then
OPENER=open
else
echo LogError: '$OPENER not found' >> "${XPLR_PIPE_MSG_IN:?}"
printf "LogError: %s\0" "$OPENER not found" >> "${XPLR_PIPE_MSG_IN:?}"
exit 1
fi
fi
(while IFS= read -r line; do
$OPENER "${line:?}" > /dev/null 2>&1
(while IFS= read -r -d '' line; do
$OPENER "${line:?}" > /dev/null 2>&1
done < "${XPLR_PIPE_RESULT_OUT:?}")
]===],
},
@ -1518,12 +1518,12 @@ xplr.config.modes.builtin.create_directory = {
PTH="$XPLR_INPUT_BUFFER"
if [ "${PTH}" ]; then
mkdir -p -- "${PTH:?}" \
&& echo SetInputBuffer: "''" >> "${XPLR_PIPE_MSG_IN:?}" \
&& echo ExplorePwd >> "${XPLR_PIPE_MSG_IN:?}" \
&& echo LogSuccess: "'$PTH created'" >> "${XPLR_PIPE_MSG_IN:?}" \
&& echo FocusPath: "'$PTH'" >> "${XPLR_PIPE_MSG_IN:?}"
&& printf "SetInputBuffer: ''\0" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf "ExplorePwd\0" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf "LogSuccess: %s\0" "$PTH created" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf "FocusPath: %s\0" "$PTH" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo PopMode >> "${XPLR_PIPE_MSG_IN:?}"
printf "PopMode\0" >> "${XPLR_PIPE_MSG_IN:?}"
fi
]===],
},
@ -1561,12 +1561,12 @@ xplr.config.modes.builtin.create_file = {
if [ "$PTH" ]; then
mkdir -p -- "$(dirname $PTH)" \
&& touch -- "$PTH" \
&& echo SetInputBuffer: "''" >> "${XPLR_PIPE_MSG_IN:?}" \
&& echo LogSuccess: "'$PTH created'" >> "${XPLR_PIPE_MSG_IN:?}" \
&& echo ExplorePwd >> "${XPLR_PIPE_MSG_IN:?}" \
&& echo FocusPath: "'$PTH'" >> "${XPLR_PIPE_MSG_IN:?}"
&& printf "SetInputBuffer: ''\0" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf "LogSuccess: %s\0" "$PTH created" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf "ExplorePwd\0" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf "FocusPath: %s\0" "$PTH" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo PopMode >> "${XPLR_PIPE_MSG_IN:?}"
printf "PopMode\0" >> "${XPLR_PIPE_MSG_IN:?}"
fi
]===],
},
@ -1668,7 +1668,7 @@ xplr.config.modes.builtin.go_to = {
elif command -v open; then
OPENER=open
else
echo LogError: '$OPENER not found' >> "${XPLR_PIPE_MSG_IN:?}"
printf "LogError: %s\0" "$OPENER not found" >> "${XPLR_PIPE_MSG_IN:?}"
exit 1
fi
fi
@ -1704,12 +1704,12 @@ xplr.config.modes.builtin.rename = {
SRC="${XPLR_FOCUS_PATH:?}"
TARGET="${XPLR_INPUT_BUFFER:?}"
if [ -e "${TARGET:?}" ]; then
echo LogError: "'$TARGET already exists'" >> "${XPLR_PIPE_MSG_IN:?}"
printf "LogError: %s\0" "$TARGET already exists" >> "${XPLR_PIPE_MSG_IN:?}"
else
mv -- "${SRC:?}" "${TARGET:?}" \
&& echo ExplorePwd >> "${XPLR_PIPE_MSG_IN:?}" \
&& echo FocusPath: "'$TARGET'" >> "${XPLR_PIPE_MSG_IN:?}" \
&& echo LogSuccess: "'$SRC renamed to $TARGET'" >> "${XPLR_PIPE_MSG_IN:?}"
&& printf "ExplorePwd\0" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf "FocusPath: %s\0" "$TARGET" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf "LogSuccess: %s\0" "$SRC renamed to $TARGET" >> "${XPLR_PIPE_MSG_IN:?}"
fi
]===],
},
@ -1746,12 +1746,12 @@ xplr.config.modes.builtin.duplicate_as = {
SRC="${XPLR_FOCUS_PATH:?}"
TARGET="${XPLR_INPUT_BUFFER:?}"
if [ -e "${TARGET:?}" ]; then
echo LogError: "'$TARGET already exists'" >> "${XPLR_PIPE_MSG_IN:?}"
printf "LogError: %s\0" "$TARGET already exists" >> "${XPLR_PIPE_MSG_IN:?}"
else
cp -r -- "${SRC:?}" "${TARGET:?}" \
&& echo ExplorePwd >> "${XPLR_PIPE_MSG_IN:?}" \
&& echo FocusPath: "'$TARGET'" >> "${XPLR_PIPE_MSG_IN:?}" \
&& echo LogSuccess: "'$SRC duplicated as $TARGET'" >> "${XPLR_PIPE_MSG_IN:?}"
&& printf "ExplorePwd\0" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf "FocusPath: %s\0" "$TARGET" >> "${XPLR_PIPE_MSG_IN:?}" \
&& printf "LogSuccess: %s\0" "$SRC duplicated as $TARGET" >> "${XPLR_PIPE_MSG_IN:?}"
fi
]===],
},
@ -1779,14 +1779,14 @@ xplr.config.modes.builtin.delete = {
messages = {
{
BashExec = [===[
(while IFS= read -r line; do
if rm -rfv -- "${line:?}"; then
echo LogSuccess: "'$line deleted'" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo LogError: "'Failed to delete $line'" >> "${XPLR_PIPE_MSG_IN:?}"
fi
(while IFS= read -r -d '' line; do
if rm -rfv -- "${line:?}"; then
printf "LogSuccess: %s\0" "$line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
else
printf "LogError: %s\0" "Failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
fi
done < "${XPLR_PIPE_RESULT_OUT:?}")
echo ExplorePwdAsync >> "${XPLR_PIPE_MSG_IN:?}"
printf "ExplorePwdAsync\0" >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
]===],
},
@ -1798,22 +1798,22 @@ xplr.config.modes.builtin.delete = {
messages = {
{
BashExec = [===[
(while IFS= read -r line; do
(while IFS= read -r -d '' line; do
if [ -d "$line" ] && [ ! -L "$line" ]; then
if rmdir -v -- "${line:?}"; then
echo LogSuccess: "'$line deleted'" >> "${XPLR_PIPE_MSG_IN:?}"
printf "LogSuccess: %s\0" "$line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo LogError: "'Failed to delete $line'" >> "${XPLR_PIPE_MSG_IN:?}"
printf "LogError: %s\0" "Failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
fi
else
if rm -v -- "${line:?}"; then
echo LogSuccess: "'$line deleted'" >> "${XPLR_PIPE_MSG_IN:?}"
printf "LogSuccess: %s\0" "$line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo LogError: "'Failed to delete $line'" >> "${XPLR_PIPE_MSG_IN:?}"
printf "LogError: %s\0" "Failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
fi
fi
done < "${XPLR_PIPE_RESULT_OUT:?}")
echo ExplorePwdAsync >> "${XPLR_PIPE_MSG_IN:?}"
printf "ExplorePwdAsync\0" >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
]===],
},
@ -2431,13 +2431,17 @@ end
xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m)
local r = m.tree .. m.prefix
local function path_escape(path)
return string.gsub(string.gsub(path, "\\", "\\\\"), "\n", "\\n")
end
if m.meta.icon == nil then
r = r .. ""
else
r = r .. m.meta.icon .. " "
end
r = r .. m.relative_path
r = r .. path_escape(m.relative_path)
if m.is_dir then
r = r .. "/"
@ -2451,7 +2455,7 @@ xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m)
if m.is_broken then
r = r .. "×"
else
r = r .. m.symlink.absolute_path
r = r .. path_escape(m.symlink.absolute_path)
if m.symlink.is_dir then
r = r .. "/"

View File

@ -17,6 +17,7 @@ pub mod pipe;
pub mod pwd_watcher;
pub mod runner;
pub mod ui;
pub mod newlines;
#[cfg(test)]
mod tests {

View File

@ -3,6 +3,7 @@ use indexmap::IndexSet;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use crate::newlines::escape_string;
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum ExternalMsg {
@ -1070,7 +1071,7 @@ impl TryFrom<&str> for ExternalMsg {
if value.starts_with('!') {
serde_yaml::from_str(value)
} else if let Some((msg, args)) = value.split_once(' ') {
let msg = format!("!{} {}", msg.trim_end_matches(':'), args);
let msg = format!("!{} {}", msg.trim_end_matches(':'), escape_string(args));
serde_yaml::from_str(&msg)
} else {
serde_yaml::from_str(value)

79
src/newlines.rs Normal file
View File

@ -0,0 +1,79 @@
struct UnescapedString<'a> {
s: std::str::Chars<'a>,
}
impl<'a> UnescapedString<'a> {
fn new(s: &'a str) -> Self {
Self { s: s.chars() }
}
}
impl Iterator for UnescapedString<'_> {
type Item = Result<char, Error>;
fn next(&mut self) -> Option<Self::Item> {
self.s.next().map(|c| match c {
'\\' => match self.s.next() {
None => Err(Error::EscapeAtEndOfString),
Some('n') => Ok('\n'),
Some('\\') => Ok('\\'),
Some(c) => Err(Error::UnrecognizedEscapedChar(c)),
},
c => Ok(c),
})
}
}
#[derive(Debug, PartialEq)]
pub enum Error {
EscapeAtEndOfString,
UnrecognizedEscapedChar(char),
}
use std::fmt;
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::EscapeAtEndOfString => {
write!(f, "Escape character at the end of the string")
}
Error::UnrecognizedEscapedChar(c) => {
write!(f, "Unrecognized escaped char: '{}'", c)
}
}
}
}
impl std::error::Error for Error {}
struct EscapedString<'a> {
s: std::str::Chars<'a>,
}
impl<'a> EscapedString<'a> {
fn new(s: &'a str) -> Self {
Self { s: s.chars() }
}
}
impl Iterator for EscapedString<'_> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
match self.s.next() {
None => None,
Some('\\') => Some(String::from("\\\\")),
Some('\n') => Some(String::from("\\n")),
Some(c) => Some(String::from(c)),
}
}
}
pub fn escape_string(s: &str) -> String {
EscapedString::new(s).collect()
}
pub fn unescape_string(s: &str) -> Result<String, Error> {
UnescapedString::new(s).collect()
}

View File

@ -65,10 +65,12 @@ pub fn read_all(pipe: &str) -> Result<Vec<ExternalMsg>> {
file.read_to_string(&mut in_str)?;
file.set_len(0)?;
let delim = '\0';
if !in_str.is_empty() {
let mut msgs = vec![];
for msg in in_str.lines() {
msgs.push(msg.trim().try_into()?);
for msg in in_str.trim_matches(delim).split(delim) {
msgs.push(msg.try_into()?);
}
Ok(msgs)
} else {

View File

@ -77,14 +77,7 @@ fn call(app: &app::App, cmd: app::Command, silent: bool) -> Result<ExitStatus> {
Command::new(cmd.command.clone())
.env("XPLR_APP_VERSION", app.version.clone())
.env("XPLR_PID", &app.pid.to_string())
.env(
"XPLR_INPUT_BUFFER",
app.input
.buffer
.as_ref()
.map(|i| i.value().to_string())
.unwrap_or_default(),
)
.env("XPLR_INPUT_BUFFER", &app.input_unescaped)
.env("XPLR_FOCUS_PATH", app.focused_node_str())
.env("XPLR_FOCUS_INDEX", focus_index)
.env("XPLR_SESSION_PATH", &app.session_path)