diff --git a/src/Files.App/Helpers/Interop/NativeFileOperationsHelper.cs b/src/Files.App/Helpers/Interop/NativeFileOperationsHelper.cs index 37df017b83c0..f0c3cd8b5308 100644 --- a/src/Files.App/Helpers/Interop/NativeFileOperationsHelper.cs +++ b/src/Files.App/Helpers/Interop/NativeFileOperationsHelper.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading; using Vanara.PInvoke; +using static Files.Core.Helpers.NativeFindStorageItemHelper; namespace Files.App.Helpers { @@ -316,6 +317,21 @@ private struct FILE_STREAM_INFO [DllImport("api-ms-win-core-file-l2-1-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] private static extern bool GetFileInformationByHandleEx(IntPtr hFile, FILE_INFO_BY_HANDLE_CLASS infoClass, IntPtr dirInfo, uint dwBufferSize); + private enum StreamInfoLevels { FindStreamInfoStandard = 0 } + + [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr FindFirstStreamW(string lpFileName, StreamInfoLevels InfoLevel, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData, uint dwFlags); + + [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool FindNextStreamW(IntPtr hndFindFile, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private class WIN32_FIND_STREAM_DATA { + public long StreamSize; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 296)] + public string cStreamName; + } + public static bool GetFileDateModified(string filePath, out FILETIME dateModified) { using var hFile = new SafeFileHandle(CreateFileFromApp(filePath, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, (uint)File_Attributes.BackupSemantics, IntPtr.Zero), true); @@ -526,29 +542,27 @@ public static string ParseSymLink(string path) return null; } + // https://stackoverflow.com/a/7988352 public static IEnumerable<(string Name, long Size)> GetAlternateStreams(string path) { - using var handle = OpenFileForRead(path); - if (!handle.IsInvalid) + WIN32_FIND_STREAM_DATA findStreamData = new WIN32_FIND_STREAM_DATA(); + IntPtr hFile = FindFirstStreamW(path, StreamInfoLevels.FindStreamInfoStandard, findStreamData, 0); + + if (hFile.ToInt64() != -1) { - var bufferSize = Marshal.SizeOf(typeof(FILE_STREAM_INFO)) * 10; - var mem = Marshal.AllocHGlobal(bufferSize); - if (GetFileInformationByHandleEx(handle.DangerousGetHandle(), FILE_INFO_BY_HANDLE_CLASS.FileStreamInfo, mem, (uint)bufferSize)) + do { - uint offset = 0; - FILE_STREAM_INFO fileStruct; - do + // The documentation for FindFirstStreamW says that it is always a ::$DATA + // stream type, but FindNextStreamW doesn't guarantee that for subsequent + // streams so we check to make sure + if (findStreamData.cStreamName.EndsWith(":$DATA") && findStreamData.cStreamName != "::$DATA") { - fileStruct = Marshal.PtrToStructure(new IntPtr(mem.ToInt64() + offset)); - var name = fileStruct.StreamName.Substring(0, (int)fileStruct.StreamNameLength / 2); - if (name.EndsWith(":$DATA") && name != "::$DATA") - { - yield return (name, fileStruct.StreamSize); - } - offset += fileStruct.NextEntryOffset; - } while (fileStruct.NextEntryOffset != 0); + yield return (findStreamData.cStreamName, findStreamData.StreamSize); + } } - Marshal.FreeHGlobal(mem); + while (FindNextStreamW(hFile, findStreamData)); + + FindClose(hFile); } } }