fix panic problem by not converting to utf-8 all the time

This commit is contained in:
Michael Zhang 2020-06-05 23:27:57 -05:00
parent 45fc4f1ee1
commit 02ba29053b
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
9 changed files with 110 additions and 27 deletions

7
Cargo.lock generated
View file

@ -77,6 +77,7 @@ dependencies = [
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
"libmount 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -156,6 +157,11 @@ dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro-error"
version = "0.4.11"
@ -388,6 +394,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
"checksum proc-macro-error 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e7959c6467d962050d639361f7703b2051c43036d03493c36f01d440fdd3138a"
"checksum proc-macro-error-attr 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e4002d9f55991d5e019fb940a90e1a95eb80c24e77cb2462dd4dc869604d543a"
"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"

View file

@ -25,3 +25,4 @@ structopt = "0.3"
thiserror = "1.0"
walkdir = "2.2"
xdg = "2.2"
percent-encoding = "2.1.0"

View file

@ -1,4 +1,6 @@
use std::ffi::OsStr;
use std::fs;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use walkdir::{DirEntry, WalkDir};
@ -113,13 +115,16 @@ impl Iterator for TrashDirIter {
entry
};
let name = entry.path().file_name().unwrap().to_str().unwrap();
let deleted_path = if !name.ends_with(".trashinfo") {
let name = match entry.path().file_name() {
Some(name) => name,
None => return None,
};
let deleted_path = if !name.as_bytes().ends_with(b".trashinfo") {
return self.next();
} else {
self.0
.join("files")
.join(name.trim_end_matches(".trashinfo"))
self.0.join("files").join(OsStr::from_bytes(
&name.as_bytes()[..name.len() - b".trashinfo".len()],
))
};
Some(TrashInfo::from_files(entry.path(), deleted_path).map_err(Error::from))
}

View file

@ -1,10 +1,14 @@
use std::ffi::OsStr;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use chrono::{DateTime, Local, TimeZone};
use percent_encoding::{percent_decode, };
use crate::errors::{Error, TrashInfoError};
use crate::utils;
const DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:%S";
@ -67,7 +71,8 @@ impl TrashInfo {
if let Some((key, value)) = parse_key_value(&line) {
match key {
"Path" => {
let value = PathBuf::from(value);
let value = percent_decode(value.as_bytes()).collect::<Vec<_>>();
let value = PathBuf::from(OsStr::from_bytes(&value));
path = Some(value)
}
"DeletionDate" => {
@ -102,7 +107,11 @@ impl TrashInfo {
/// Write the current TrashInfo into a .trashinfo file.
pub fn write(&self, mut out: impl Write) -> Result<(), io::Error> {
writeln!(out, "[Trash Info]")?;
writeln!(out, "Path={}", self.path.to_str().unwrap())?;
writeln!(
out,
"Path={}",
utils::percent_encode(&self.path)
)?;
writeln!(
out,
"DeletionDate={}",

View file

@ -12,12 +12,14 @@ extern crate anyhow;
#[macro_use]
extern crate thiserror;
#[macro_use]
mod utils;
mod dir;
mod errors;
mod info;
mod mounts;
pub mod ops;
mod utils;
use std::path::PathBuf;

View file

@ -3,6 +3,7 @@ use std::path::PathBuf;
use anyhow::Result;
use crate::TrashDir;
use crate::utils;
/// Options to pass to list
#[derive(StructOpt)]
@ -30,7 +31,7 @@ pub fn list(options: ListOptions) -> Result<()> {
.collect::<Vec<_>>();
files.sort_unstable_by_key(|info| info.deletion_date);
for info in files {
println!("{}\t{}", info.deletion_date, info.path.to_str().unwrap());
println!("{}\t{}", info.deletion_date, utils::percent_encode(info.path));
}
Ok(())

View file

@ -7,20 +7,23 @@ use std::path::{Path, PathBuf};
use anyhow::Result;
use chrono::Local;
use crate::utils;
use crate::utils::{self};
use crate::{TrashDir, TrashInfo};
use crate::{HOME_MOUNT, MOUNTS};
#[derive(Debug, Error)]
pub enum Error {
#[error("Refusing to remove directory {0} without '-r' option")]
MissingRecursiveOption(PathBuf),
// #[error("Refusing to remove directory {0} without '-r' option")]
// MissingRecursiveOption(PathBuf),
#[error("Refusing to remove '.' or '..', skipping...")]
CannotTrashDotDirs,
#[error("Cancelled by user.")]
CancelledByUser,
#[error("Invalid filename.")]
InvalidFilename,
}
/// Options to pass to put
@ -153,19 +156,13 @@ impl DeletionStrategy {
let target = target.as_ref();
// this will be None if target isn't a symlink
let link_info = target.read_link().ok();
// file is a directory
// if !link_info.is_some() && target.is_dir() && !options.recursive {
// bail!(Error::MissingRecursiveOption(target.to_path_buf()));
// }
let _link_info = target.read_link().ok();
let (trash_dir, requires_copy) = self.get_target_trash();
// prompt if not suppressed
// TODO: streamline this logic better
if !options.force && (requires_copy || options.prompt) {
// TODO: actually handle prompting instead of manually flushing
if requires_copy {
eprint!(
"Removing file '{}' requires potentially expensive copying. Continue? [Y/n] ",
@ -174,6 +171,7 @@ impl DeletionStrategy {
} else if options.prompt {
eprint!("Remove file '{}'? [Y/n] ", target.to_str().unwrap());
}
// TODO: actually handle prompting instead of manually flushing
io::stderr().flush()?;
let should_continue = loop {
@ -196,14 +194,27 @@ impl DeletionStrategy {
// preparing metadata
let now = Local::now();
let elapsed = now.timestamp_millis();
let file_name = format!(
"{}.{}",
elapsed,
target.file_name().unwrap().to_str().unwrap()
);
let elapsed_str = elapsed.to_string();
let target_file = match target.file_name() {
Some(file) => file.to_os_string(),
None => bail!(Error::InvalidFilename),
};
let file_name = crate::concat_os_str!(elapsed_str, ".", target_file);
// {
// let buf = vec![0; elapsed_str.len() + 1 + target_file.len()];
// unimplemented!()
// // format!(
// // "{}.{}",
// // elapsed,
// // target.file_name().unwrap().to_str().unwrap()
// // )
// };
let trash_file_path = trash_dir.files_dir()?.join(&file_name);
let trash_info_path = trash_dir.info_dir()?.join(file_name + ".trashinfo");
let trash_info_path = trash_dir
.info_dir()?
.join(crate::concat_os_str!(file_name, ".trashinfo"));
let trash_info = TrashInfo {
path: utils::into_absolute(target)?,

View file

@ -5,6 +5,7 @@ use std::path::PathBuf;
use anyhow::Result;
use crate::TrashDir;
use crate::utils;
/// Options to pass to restore
#[derive(StructOpt)]
@ -50,7 +51,7 @@ pub fn restore(options: RestoreOptions) -> Result<()> {
"[{}]\t{}\t{}",
i,
info.deletion_date,
info.path.to_str().unwrap()
utils::percent_encode(&info.path)
);
}

View file

@ -1,10 +1,15 @@
use std::env;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::path::{Path, PathBuf};
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf, MAIN_SEPARATOR};
use anyhow::Result;
use percent_encoding::{AsciiSet};
use walkdir::WalkDir;
const MASK: &AsciiSet = percent_encoding::CONTROLS;
pub fn into_absolute(path: impl AsRef<Path>) -> Result<PathBuf> {
let path = path.as_ref();
@ -49,3 +54,44 @@ pub fn recursive_copy(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()
Ok(())
}
/// Percent-encodes a path, but only the file names, not the separators.
pub fn percent_encode(path: impl AsRef<Path>) -> String {
let path = path.as_ref();
path.iter()
.map(|segment| {
percent_encoding::percent_encode(segment.as_bytes(), MASK).to_string()
})
.collect::<Vec<_>>()
.join(&MAIN_SEPARATOR.to_string())
}
pub(crate) fn concat_os_str_iter<'a>(ss: &Vec<&'a OsStr>) -> OsString {
let mut len = 0;
for s in ss {
len += s.len();
}
let mut buf = vec![0; len];
let mut c = 0;
for s in ss {
let segment = &mut buf[c..c + s.len()];
segment.copy_from_slice(s.as_bytes());
c += s.len();
}
OsStr::from_bytes(&buf).to_os_string()
}
/// Concatenates an arbitrary number of OsStrs
#[macro_export]
macro_rules! concat_os_str {
($($str:expr),* $(,)?) => {
{
let _wtf = vec![
$($str.as_ref(),)*
];
crate::utils::concat_os_str_iter(&_wtf)
}
};
}