{"record":{"id":"10a7a14300d8c29a","repo":"sxyazi/yazi","slug":"invalid-trash-info-path","errorCode":null,"errorMessage":"invalid trash info path","messagePattern":"invalid trash info path","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"yazi-fs/src/trash/freedesktop/trash_info.rs","lineNumber":17,"sourceCode":"use std::{borrow::Cow, ffi::OsStr, fs::File, io::{self, BufRead, BufReader}, os::unix::ffi::OsStrExt, path::{Path, PathBuf}};\n\nuse percent_encoding::percent_decode;\nuse yazi_shim::{Uzers, path::PathExt};\n\npub(super) struct TrashInfo {\n\tpub(super) root:     PathBuf,\n\tpub(super) backing:  PathBuf,\n\tpub(super) original: PathBuf,\n}\n\nimpl TrashInfo {\n\t// Parses from a trashinfo path, e.g.:\n\t//   /home/alice/.local/share/Trash/info/cat.jpg.trashinfo\n\tpub(super) fn parse(info: &Path) -> io::Result<Self> {\n\t\tif info.extension() != Some(OsStr::new(\"trashinfo\")) {\n\t\t\treturn Err(io::Error::new(io::ErrorKind::InvalidData, \"invalid trash info path\"));\n\t\t}\n\n\t\t// /home/alice/.local/share/Trash\n\t\tlet root = info\n\t\t\t.parent()\n\t\t\t.filter(|p| p.file_name() == Some(OsStr::new(\"info\")))\n\t\t\t.and_then(Path::parent)\n\t\t\t.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, \"invalid trash info path\"))?;\n\n\t\t// cat.jpg\n\t\tlet stem = info\n\t\t\t.file_stem()\n\t\t\t.filter(|&stem| stem != OsStr::new(\".\") && stem != OsStr::new(\"..\"))\n\t\t\t.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, \"invalid trash info path\"))?;\n\n\t\tlet original = Self::parse_original(info, root)?;\n\t\tif original.file_name().is_none() {\n\t\t\treturn Err(io::Error::new(io::ErrorKind::InvalidData, \"invalid original trash path\"));","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/sxyazi/yazi/blob/a541adde4e5b082719a0f21a87eb3eab071ece58/yazi-fs/src/trash/freedesktop/trash_info.rs#L1-L35","documentation":"TrashInfo::parse expects a path to a freedesktop .trashinfo file (e.g. .../info/cat.jpg.trashinfo). It first checks that the extension is exactly \"trashinfo\" and returns InvalidData \"invalid trash info path\" otherwise, then derives the trash root by expecting the parent directory to be named \"info\". Passing anything but a proper trashinfo file path fails here.","triggerScenarios":"Calling TrashInfo::parse with a path lacking the .trashinfo extension; passing a files/ entry (the trashed data) instead of the info/ record; scanning a directory that mixes records and other files; ids pointing at info/ itself rather than a record inside it.","commonSituations":"Directory iteration that doesn't filter by extension before parsing; a user-created file inside info/ without the .trashinfo suffix being picked up by a scanner; mixing up files/ and info/ subdirectories of a freedesktop trash.","solutions":["Pass the path inside the trash's info/ directory that ends with .trashinfo, not the files/ entry.","Filter directory listings with extension == \"trashinfo\" before calling parse.","Derive ids through the trash provider's listing APIs (tops/list) instead of hand-building paths."],"exampleFix":"// before\nlet info = TrashInfo::parse(&trash_dir.join(\"files\").join(\"cat.jpg\"))?;\n// after\nlet info = TrashInfo::parse(&trash_dir.join(\"info\").join(\"cat.jpg.trashinfo\"))?;","handlingStrategy":"validation","validationCode":"// Rust\nfn is_trashinfo_path(p: &Path) -> bool {\n    p.extension() == Some(std::ffi::OsStr::new(\"trashinfo\")) && p.parent().is_some_and(|p| p.file_name() == Some(std::ffi::OsStr::new(\"info\")))\n}","typeGuard":null,"tryCatchPattern":"match TrashInfo::parse(path) {\n    Err(e) if e.to_string().contains(\"invalid trash info path\") => continue, // not a trash record; skip\n    other => other?,\n}","preventionTips":["Filter by .trashinfo extension before parsing.","Pass paths under info/, never under files/.","Derive ids via the trash provider's listing APIs rather than hand-built paths."],"tags":["trash","freedesktop","path","invalid-data","validation"],"backgroundTag":"invalid-trash-info-path","analyzedSha":"a541adde4e5b082719a0f21a87eb3eab071ece58","analyzedAt":"2026-09-02T18:38:25.566Z","contentChangedAt":"2026-09-02T18:38:25.566Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}