233 lines
5.7 KiB
Rust
233 lines
5.7 KiB
Rust
//! The various formats that [fif](crate) can output to.
|
|
|
|
use std::io::{self, Write};
|
|
#[cfg(unix)]
|
|
use std::os::unix::ffi::OsStrExt;
|
|
use std::path::Path;
|
|
|
|
use snailquote::escape;
|
|
|
|
use crate::scan_error::ScanError;
|
|
use crate::{Findings, BACKEND};
|
|
use std::ffi::OsStr;
|
|
|
|
/// The current version of fif, as defined in Cargo.toml.
|
|
const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
|
|
|
|
#[doc(hidden)]
|
|
type Entries<'a> = [Result<Findings<'a>, ScanError<'a>>];
|
|
|
|
enum Writable<'a> {
|
|
String(&'a str),
|
|
Path(&'a Path),
|
|
Space,
|
|
Newline,
|
|
}
|
|
|
|
// the lifetime of a lifetime
|
|
impl<'a> From<&'a str> for Writable<'a> {
|
|
fn from(s: &'a str) -> Writable<'a> {
|
|
Writable::String(s)
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a Path> for Writable<'a> {
|
|
fn from(p: &'a Path) -> Writable<'a> {
|
|
Writable::Path(p)
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a OsStr> for Writable<'a> {
|
|
fn from(p: &'a OsStr) -> Writable<'a> {
|
|
Writable::Path(p.as_ref())
|
|
}
|
|
}
|
|
|
|
fn smart_write<W: Write>(f: &mut W, writeables: &[Writable]) -> io::Result<()> {
|
|
// ehhhh
|
|
for writeable in writeables {
|
|
match writeable {
|
|
Writable::Space => write!(f, " ")?,
|
|
Writable::Newline => writeln!(f,)?,
|
|
Writable::String(s) => write!(f, "{}", s)?,
|
|
Writable::Path(path) => {
|
|
if let Some(string) = path.to_str() {
|
|
write!(f, "{}", escape(string))?
|
|
} else {
|
|
write!(f, "'")?;
|
|
#[cfg(unix)]
|
|
f.write_all(&*path.as_os_str().as_bytes())?;
|
|
// TODO: implement bonked strings for windows
|
|
// something like:
|
|
// f.write_all(&*path.as_os_str().encode_wide().collect::<Vec<u16>>())?;
|
|
#[cfg(windows)]
|
|
write!(f, "{}", path.as_os_str().to_string_lossy())?;
|
|
write!(f, "'")?
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub trait Format {
|
|
fn new() -> Self;
|
|
fn rename<W: Write>(&self, f: &mut W, from: &Path, to: &Path) -> io::Result<()>;
|
|
fn no_known_extension<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()>;
|
|
fn unreadable<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()>;
|
|
fn unknown_type<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()>;
|
|
fn header<W: Write>(&self, entries: &Entries, f: &mut W) -> io::Result<()>;
|
|
fn footer<W: Write>(&self, entries: &Entries, f: &mut W) -> io::Result<()>;
|
|
|
|
fn write_all<W: Write>(&self, entries: &Entries, f: &mut W) -> io::Result<()> {
|
|
// TODO: clean this up - it's kinda messy
|
|
self.header(entries, f)?;
|
|
|
|
for entry in entries {
|
|
match entry {
|
|
Ok(finding) => {
|
|
if let Some(ext) = finding.recommended_extension() {
|
|
self.rename(f, finding.file, &finding.file.with_extension(ext.as_str()))?
|
|
} else {
|
|
self.no_known_extension(f, finding.file)?
|
|
}
|
|
}
|
|
|
|
Err(error) => {
|
|
// something went wrong 0uo
|
|
match error {
|
|
// failed to read the file
|
|
ScanError::File(path) => self.unreadable(f, path)?,
|
|
// file was read successfully, but we couldn't determine a mimetype
|
|
ScanError::Mime(path) => self.unknown_type(f, path)?,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.footer(entries, f)
|
|
}
|
|
}
|
|
|
|
/// Bourne-Shell compatible script.
|
|
pub struct Script {}
|
|
|
|
impl Format for Script {
|
|
fn new() -> Self {
|
|
Self {}
|
|
}
|
|
|
|
fn rename<W: Write>(&self, f: &mut W, from: &Path, to: &Path) -> io::Result<()> {
|
|
smart_write(
|
|
f,
|
|
&[
|
|
"mv -v -i -- ".into(),
|
|
from.into(),
|
|
Writable::Space,
|
|
to.into(),
|
|
Writable::Newline,
|
|
],
|
|
)
|
|
}
|
|
|
|
fn no_known_extension<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
|
|
smart_write(
|
|
f,
|
|
&["echo No known extension for ".into(), path.into(), Writable::Newline],
|
|
)
|
|
}
|
|
|
|
fn unreadable<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
|
|
smart_write(f, &["# Failed to read ".into(), path.into(), Writable::Newline])
|
|
}
|
|
|
|
fn unknown_type<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
|
|
smart_write(
|
|
f,
|
|
&[
|
|
"# Failed to detect mime type for ".into(),
|
|
path.into(),
|
|
Writable::Newline,
|
|
],
|
|
)
|
|
}
|
|
|
|
fn header<W: Write>(&self, _: &Entries, f: &mut W) -> io::Result<()> {
|
|
writeln!(
|
|
f,
|
|
"#!/usr/bin/env sh\n# Generated by fif {} ({} backend)",
|
|
VERSION.unwrap_or("???"),
|
|
BACKEND
|
|
)?;
|
|
writeln!(f, "\nset -e\n")
|
|
}
|
|
|
|
fn footer<W: Write>(&self, _: &Entries, f: &mut W) -> io::Result<()> {
|
|
writeln!(f, "\necho 'Done.'")
|
|
}
|
|
}
|
|
|
|
// PowerShell is a noun, not a type
|
|
#[allow(clippy::doc_markdown)]
|
|
/// PowerShell script.
|
|
pub struct PowerShell {}
|
|
|
|
impl Format for PowerShell {
|
|
fn new() -> Self {
|
|
Self {}
|
|
}
|
|
|
|
fn rename<W: Write>(&self, f: &mut W, from: &Path, to: &Path) -> io::Result<()> {
|
|
// unfortunately there doesn't seem to be an equivalent of sh's `mv -i` -- passing the '-Confirm' flag will prompt
|
|
// the user to confirm every single rename, and using Move-Item -Force will always overwrite without prompting.
|
|
// there doesn't seem to be a way to rename the file, prompting only if the target already exists.
|
|
smart_write(
|
|
f,
|
|
&[
|
|
"Rename-Item -Path ".into(),
|
|
from.into(),
|
|
" -NewName ".into(),
|
|
to.file_name().unwrap().into(),
|
|
Writable::Newline,
|
|
],
|
|
)
|
|
}
|
|
|
|
fn no_known_extension<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
|
|
smart_write(
|
|
f,
|
|
&[
|
|
"Write-Output @'\nNo known extension for ".into(),
|
|
path.into(),
|
|
"\n'@".into(),
|
|
],
|
|
)
|
|
}
|
|
|
|
fn unreadable<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
|
|
smart_write(
|
|
f,
|
|
&["Write-Output @'\nFailed to read ".into(), path.into(), "\n'@".into()],
|
|
)
|
|
}
|
|
|
|
fn unknown_type<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
|
|
smart_write(
|
|
f,
|
|
&["<# Failed to detect mime type for ".into(), path.into(), "#>".into()],
|
|
)
|
|
}
|
|
|
|
fn header<W: Write>(&self, _: &Entries, f: &mut W) -> io::Result<()> {
|
|
writeln!(
|
|
f,
|
|
"#!/usr/bin/env pwsh\n# Generated by fif {} ({} backend)",
|
|
VERSION.unwrap_or("???"),
|
|
BACKEND
|
|
)
|
|
}
|
|
|
|
fn footer<W: Write>(&self, _: &Entries, f: &mut W) -> io::Result<()> {
|
|
writeln!(f, "\nWrite-Output 'Done!'")
|
|
}
|
|
}
|