Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file.
- The Site Manager has its Browse button back for choosing a private key file, and it opens where the current path points rather than at your home folder.

### Fixed
- Downloading a folder no longer hangs until the app runs out of memory when the server lists that folder (or its parent) as one of its own children, or when a directory symlink points back at a folder already being copied. Large folders are listed once and then copied; a loop in the listing is skipped rather than followed forever.
- Connecting to an unknown SSH host with "Ask before trusting a new server" (the default) now shows a dialog to reject the key, accept it once, or accept it permanently, instead of refusing every new host. Reject is the default, so Enter and Escape both refuse.
- Starting Portkey Drop opened an empty terminal window behind it. The program was built as a console application, so Windows gave it a console whether it wanted one or not; it is now built as a windowed application and starts with nothing but its own window. `--version` and `--help` still print to the terminal you run them from.
- The update offer said "Current: 0.6.0" on a nightly build, which is the version of the release before it and the same for every nightly. It now names the running build the way About does, so you can see which nightly you are on and which one is being offered.
Expand Down
21 changes: 18 additions & 3 deletions crates/portkeydrop-core/src/protocols/ftp/listing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,14 @@ pub fn parse_mlsd_line(line: &str, parent: &str) -> Option<RemoteFile> {
}
}

// `cdir` and `pdir` are the listed directory and its parent; both are
// directories, and the caller filters the self-reference by name.
let is_dir = matches!(entry_type.as_str(), "dir" | "cdir" | "pdir");
// `cdir` is the directory being listed and `pdir` is its parent. Some
// servers name them `.` and `..`; RFC 3659 also allows the real path.
// Either way they are not children — walking them as directories loops
// until the process runs out of memory.
if matches!(entry_type.as_str(), "cdir" | "pdir") {
return None;
}
let is_dir = entry_type == "dir";

Some(RemoteFile {
name: name.to_string(),
Expand Down Expand Up @@ -228,6 +233,16 @@ mod tests {
assert!(parse_mlsd_line("type=pdir; ..", "/home").is_none());
}

#[test]
fn mlsd_cdir_and_pdir_with_real_paths_are_skipped() {
// RFC 3659 allows the current and parent directories to be named as
// their real paths, not only `.` and `..`. Treating those as children
// is what made a folder download walk forever.
assert!(parse_mlsd_line("type=cdir; /home/user", "/home/user").is_none());
assert!(parse_mlsd_line("type=pdir; /home", "/home/user").is_none());
assert!(parse_mlsd_line("type=cdir; user", "/home/user").is_none());
}

#[test]
fn mlsd_names_may_contain_semicolons_and_spaces() {
let entry = parse_mlsd_line("type=file;size=1; weird; name.txt", "/home").unwrap();
Expand Down
10 changes: 10 additions & 0 deletions crates/portkeydrop-core/src/protocols/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,16 @@ pub trait TransferClient: Send {
let parent = path::parent(self.cwd());
self.chdir(&parent)
}

/// The real path of `path`, following directory symlinks.
///
/// Recursive walks use this as an identity so a symlink or listing that
/// points back at a directory already seen is not listed again. Protocols
/// without a realpath operation return the normalised path.
fn canonicalize(&mut self, path: &str) -> Result<String> {
let _ = self;
Ok(path::normalize(path))
}
}

/// Build the client for a connection.
Expand Down
12 changes: 12 additions & 0 deletions crates/portkeydrop-core/src/protocols/sftp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,18 @@ impl TransferClient for SftpClient {
&metadata,
))
}

fn canonicalize(&mut self, remote_path: &str) -> Result<String> {
let session = self.session()?;
let target = path::resolve(&self.cwd, remote_path);
let probe = target.clone();
self.block_on(async move {
session
.canonicalize(probe.clone())
.await
.map_err(|err| map_sftp_error(err, &probe))
})?
}
}

/// Create a remote directory and any missing parents.
Expand Down
Loading