An E record received before any D record made the directory depth
counter negative and moved the current directory to the parent of the
destination, so that a following C record wrote outside it; each further
E record climbed another level. On Windows, a D record named "...", ".. "
or " .." (legal names on a Unix server) normalizes to the current
directory, so a D and E pair climbed one level with a balanced counter.
An unmatched E record is now rejected before it is acknowledged. Every C
and D record name goes through ResolveChild, which requires the full path
to be a direct child of the current directory with exactly the name that
was sent. This also rejects Windows device names such as "CON", and names
that Windows would silently change, such as a trailing dot, unless the
destination uses the \\?\ prefix. Rejected names are reported to the
server and thrown as ScpException. ResolveChild replaces
EnsureValidLocalName.
The C, D and T record patterns were not anchored and D was tried before
C, so a file named, for example, "zD0755 0 x" was treated as a directory
and its content read as SCP records, letting whoever controls a file on
the server choose the names and contents of files written locally.
The patterns now match the whole line. An invalid or unexpected record is
reported to the server and thrown as ScpException; previously an unknown
record was reported and ignored, and Download(string, Stream) then returned
normally with an empty stream.
Download(string, FileInfo) no longer shares the directory loop. It
accepts an optional T record followed by a C record, so a D or E record
can no longer cause an InvalidCastException or let later C records
overwrite the file. The directory loop moves to DownloadDirectoryContent
and the content copy is renamed DownloadFileContent.