Skip to content

Commit 2b32b77

Browse files
committed
enhance error handling for drag operations and add tests for path validation
1 parent 04a511b commit 2b32b77

2 files changed

Lines changed: 74 additions & 13 deletions

File tree

crates/drag/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ pub enum Error {
9393
#[cfg(windows)]
9494
#[error("{0}")]
9595
WindowsError(#[from] windows::core::Error),
96+
#[cfg(windows)]
97+
#[error("failed to resolve path for drag operation: {0}")]
98+
InvalidShellPath(std::path::PathBuf),
9699
#[error(transparent)]
97100
Io(#[from] std::io::Error),
98101
#[error("unsupported window handle")]

crates/drag/src/platform_impl/windows/mod.rs

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ pub fn start_drag<W: HasWindowHandle, F: Fn(DragResult, CursorPosition) + Send +
235235
paths.push(dunce::canonicalize(f)?);
236236
}
237237

238-
let data_object: IDataObject = get_file_data_object(&paths).unwrap();
238+
let data_object: IDataObject = get_file_data_object(&paths)?;
239239
let drop_source: IDropSource = DropSource::new().into();
240240

241241
unsafe {
@@ -276,7 +276,7 @@ pub fn start_drag<W: HasWindowHandle, F: Fn(DragResult, CursorPosition) + Send +
276276

277277
let paths = vec![dunce::canonicalize("./")?];
278278

279-
let data_object: IDataObject = get_file_data_object(&paths).unwrap();
279+
let data_object: IDataObject = get_file_data_object(&paths)?;
280280
let drop_source: IDropSource = DummyDropSource::new().into();
281281

282282
unsafe {
@@ -365,26 +365,84 @@ pub fn create_instance<T: Interface + ComInterface>(clsid: &GUID) -> Result<T> {
365365
unsafe { CoCreateInstance(clsid, None, CLSCTX_ALL) }
366366
}
367367

368-
fn get_file_data_object(paths: &[PathBuf]) -> Option<IDataObject> {
368+
fn get_file_data_object(paths: &[PathBuf]) -> crate::Result<IDataObject> {
369369
unsafe {
370-
let shell_item_array = get_shell_item_array(paths).unwrap();
371-
shell_item_array.BindToHandler(None, &BHID_DataObject).ok()
370+
let shell_item_array = get_shell_item_array(paths)?;
371+
Ok(shell_item_array.BindToHandler(None, &BHID_DataObject)?)
372372
}
373373
}
374374

375-
fn get_shell_item_array(paths: &[PathBuf]) -> Option<IShellItemArray> {
375+
fn get_shell_item_array(paths: &[PathBuf]) -> crate::Result<IShellItemArray> {
376376
unsafe {
377-
let list: Vec<*const Common::ITEMIDLIST> = paths
378-
.iter()
379-
.map(|path| get_file_item_id(path).cast_const())
380-
.collect();
381-
SHCreateShellItemArrayFromIDLists(&list).ok()
377+
let mut list: Vec<*const Common::ITEMIDLIST> = Vec::with_capacity(paths.len());
378+
for path in paths {
379+
match get_file_item_id(path) {
380+
Ok(pidl) => list.push(pidl.cast_const()),
381+
Err(e) => {
382+
for pidl in &list {
383+
CoTaskMemFree(Some(*pidl as *const _ as *mut _));
384+
}
385+
return Err(e);
386+
}
387+
}
388+
}
389+
let result = SHCreateShellItemArrayFromIDLists(&list);
390+
for pidl in &list {
391+
CoTaskMemFree(Some(*pidl as *const _ as *mut _));
392+
}
393+
Ok(result?)
382394
}
383395
}
384396

385-
fn get_file_item_id(path: &Path) -> *mut Common::ITEMIDLIST {
397+
fn get_file_item_id(path: &Path) -> crate::Result<*mut Common::ITEMIDLIST> {
386398
unsafe {
387399
let wide_path: Vec<u16> = path.as_os_str().encode_wide().chain(once(0)).collect();
388-
windows::Win32::UI::Shell::ILCreateFromPathW(PCWSTR::from_raw(wide_path.as_ptr()))
400+
let pidl =
401+
windows::Win32::UI::Shell::ILCreateFromPathW(PCWSTR::from_raw(wide_path.as_ptr()));
402+
if pidl.is_null() {
403+
Err(crate::Error::InvalidShellPath(path.to_path_buf()))
404+
} else {
405+
Ok(pidl)
406+
}
407+
}
408+
}
409+
410+
#[cfg(test)]
411+
mod tests {
412+
use super::*;
413+
414+
#[test]
415+
fn get_file_item_id_returns_error_for_nonexistent_path() {
416+
let path = PathBuf::from(r"C:\__nonexistent_drag_rs_test_path__\file.txt");
417+
let result = get_file_item_id(&path);
418+
assert!(result.is_err());
419+
}
420+
421+
#[test]
422+
fn get_file_item_id_returns_error_for_unc_nonexistent() {
423+
let path = PathBuf::from(r"\\nonexistent_server_drag_rs\share\file.png");
424+
let result = get_file_item_id(&path);
425+
assert!(result.is_err());
426+
}
427+
428+
#[test]
429+
fn get_shell_item_array_returns_error_for_invalid_paths() {
430+
let paths = vec![PathBuf::from(r"C:\__nonexistent_drag_rs_test__\a.txt")];
431+
let result = get_shell_item_array(&paths);
432+
assert!(result.is_err());
433+
}
434+
435+
#[test]
436+
fn get_file_data_object_returns_error_not_panic_for_bad_paths() {
437+
let paths = vec![PathBuf::from(r"\\nonexistent_server\share\file.png")];
438+
let result = get_file_data_object(&paths);
439+
assert!(result.is_err());
440+
}
441+
442+
#[test]
443+
fn get_file_item_id_succeeds_for_existing_file() {
444+
let path = PathBuf::from(r"C:\Windows\System32\notepad.exe");
445+
let result = get_file_item_id(&path);
446+
assert!(result.is_ok());
389447
}
390448
}

0 commit comments

Comments
 (0)