* Do not follow linked directories in SafeGetFiles outside of Windows
SafeGetFiles with SearchOption.AllDirectories follows directory symlinks.
On Linux, the UDF view of some PC/Mac hybrid discs has symlinks whose
target reads as empty, and each one resolves to its own folder, so the
recursion never ends (fixes#2, SabreTools/BinaryObjectScanner#276).
Outside of Windows, walk the tree one directory at a time and do not enter
directories that carry FileAttributes.ReparsePoint. Files are matched with
Directory.GetFiles per directory, so the pattern behaves as before, and the
order is the same breadth-first order. Windows keeps Directory.GetFiles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Do not follow linked directories in the other AllDirectories overloads
SafeGetDirectories and the SafeEnumerateDirectories, SafeEnumerateFiles
and SafeEnumerateFileSystemEntries wrappers follow directory symlinks in
the same way. On Linux, with one directory linked to itself,
SafeEnumerateFiles returns 43 files instead of 2, and with three such
links it is still running after 10 seconds.
The walk from SafeGetFiles is now a lazy iterator that all of them share,
so the Enumerate wrappers stay lazy. Each directory is listed with the
matching overload without SearchOption (Directory.GetFiles,
SafeEnumerateFiles and so on), so patterns and inaccessible directories
behave as in the recursive search. If the attributes of a subdirectory
cannot be read, such as below a directory without execute permission, it
is entered anyway, so it fails in the same way as in the recursive search.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check for linked directories without catching exceptions
DirectoryInfo.Exists returns false where the attributes cannot be read, and the criteria for the walk are checked one at a time with their reasons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Decode short codes at the end of the inflate input
InflateCodes waited for as many bits as its lookup table uses before
it decoded a literal, length or distance code. When the input ended
with fewer bits than that, the last codes were not decoded, even though
they were complete. An MS-ZIP block is a deflate stream on its own, so
its last match was lost and the block came out short (#1).
When no more input is there, the code is now looked up with the bits
that are left and used if it is not longer than those.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep a full 32 KiB inflate dictionary
SetDictionary kept only the last 32767 bytes of a 32 KiB dictionary,
because the window needs one free byte between its read and write
positions. MS-ZIP sets the previous 32 KiB block as the dictionary,
and a match at the start of a block can copy from 32768 bytes back,
which then read a byte that was not in the window.
A dictionary that fills the window now starts with the read and write
positions at 0, so the first byte written replaces the oldest byte of
the dictionary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Disposing a ReadOnlyCompositeStream did not dispose the streams it
reads from, so files opened only for the composite stayed open until
their streams were finalized. WiseOverlayHeader.OpenFile in
SabreTools.Serialization opens a Wise installer and all of its parts
this way.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Parse doubles with the invariant culture
ConvertToDouble used double.TryParse with the current culture, so a
value like "59.940000" became 59940000 on a German system and null on a
French one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop the culture reset from the ConvertToDouble test
A culture set inside a test does not carry over to the next test, so
the reset and the try/finally around it are not needed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Allow 0-byte reads at the end of a byte array
ReadBytes(ref offset, 0) threw ArgumentOutOfRangeException when offset
was equal to the array length, before the 0-byte case was handled. The
same happened for a 0-byte read from an empty array, and TryReadBytes
with a count of 0 at the end threw instead of returning true. The offset
check now accepts the end of the array for a 0-byte read only; reads past
the end still throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Handle 0-byte reads before the offset check
Same order as the stream version of ReadExactlyToBuffer, applied to
both byte array copies (Numerics.Extensions and IO.Extensions).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This is being done to avoid any API-breaking changes in the future. If the values are set explicitly, added values either need to force a major version update or get added as the next-highest values.