AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
26.09k stars 2.26k forks source link

unable to decompress a stream from assets #13604

Open landrufang opened 1 year ago

landrufang commented 1 year ago

Describe the bug

i have a zip file in assets. when i try to read it and decompress, something exception hanppens. here is my code

using (var stream = AssetLoader.Open(new Uri("avares://xxx/Assets/publish.zip")))
{
    if (stream == null)
        throw new Exception($"No resource");
    using (ZipArchive archive = new ZipArchive(stream))
    {
      //some code
    }
}

Exception throws at ZipArchive archive = new ZipArchive(stream) Exception message: System.ArgumentOutOfRangeException:“Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection. Arg_ParamName_Name”

and here is stacktrace

在 System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
   在 System.IO.UnmanagedMemoryStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   在 Avalonia.Platform.Internal.SlicedStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   在 System.IO.Stream.Read(Span`1 buffer)
   在 System.IO.Stream.ReadAtLeastCore(Span`1 buffer, Int32 minimumBytes, Boolean throwOnEndOfStream)
   在 System.IO.Stream.ReadAtLeast(Span`1 buffer, Int32 minimumBytes, Boolean throwOnEndOfStream)
   在 System.IO.Compression.ZipHelper.ReadBytes(Stream stream, Byte[] buffer, Int32 bytesToRead)
   在 System.IO.Compression.ZipHelper.SeekBackwardsAndRead(Stream stream, Byte[] buffer, Int32& bufferPointer)
   在 System.IO.Compression.ZipHelper.SeekBackwardsToSignature(Stream stream, UInt32 signatureToFind, Int32 maxBytesToRead)
   在 System.IO.Compression.ZipArchive.ReadEndOfCentralDirectory()
   在 System.IO.Compression.ZipArchive..ctor(Stream stream, ZipArchiveMode mode, Boolean leaveOpen, Encoding entryNameEncoding)
   在 System.IO.Compression.ZipArchive..ctor(Stream stream)
   在 ZXInstaller.ViewModels.MainViewModel.InstallAction() 在 xxx\MainViewModel.cs 中: 第 58 行
   在 ReactiveUI.ReactiveCommand.<>c__DisplayClass0_0.<Create>b__1(IObserver`1 observer)
   在 System.Reactive.Linq.QueryLanguage.CreateWithDisposableObservable`1.SubscribeCore(IObserver`1 observer)
   在 System.Reactive.ObservableBase`1.Subscribe(IObserver`1 observer)
robloo commented 1 year ago

I can confirm this on my end. It was encountered a few months ago porting some unit tests over to Avalonia.

The following code will not work for .zip files. It will work for all other file types tested (such as XML or TXT).

            var assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
            var baseUri = "avares://" + assemblyName + "/TestFiles/Native/";

            foreach (var fileName in fileNames)
            {
                using (Stream stream = AssetLoader.Open(new Uri(baseUri + fileName)))
                {
                    // Test
                }
            }
timunie commented 1 year ago

one may need to seek the stream to pos 0

stream.Seek(0);
robloo commented 1 year ago

Good idea but resetting the stream position doesn't seem to fix this. That is already done in test code on my end using stream.Position = 0;.

timunie commented 1 year ago

@robloo can you attach that test for us to double-check?

robloo commented 1 year ago

@timunie I wish but can't for a few reasons 1) it's testing IO of the apps native file format. 2) It goes deep into the internal workings of the app and object model.

Should be pretty easy for me to reproduce it though. I'll just zip a text file and try reading the contents.

timunie commented 1 year ago

I found that a "SlicedStream" is returned which is an Avalonia class. If I copy the data to a MemoryStream, it loads the zip file as expected. Don't know the root cause here but may give someone a clue where to look.

https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Base/Platform/Internal/SlicedStream.cs

using (var stream = AssetLoader.Open(new Uri("avares://AvaloniaApplication1/Assets/Test.zip", UriKind.RelativeOrAbsolute)))
{
    stream.Seek(0, SeekOrigin.Begin);

    using (var ms = new MemoryStream())
    {
        stream.CopyTo(ms);

        var zip = new ZipArchive(ms, ZipArchiveMode.Read);
        Debug.WriteLine(zip.Entries.Count); // prints 1 as expected as we have one entry in our test zip
    }
}
landrufang commented 1 year ago

I found that a "SlicedStream" is returned which is an Avalonia class. If I copy the data to a MemoryStream, it loads the zip file as expected. Don't know the root cause here but may give someone a clue where to look.

https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Base/Platform/Internal/SlicedStream.cs

using (var stream = AssetLoader.Open(new Uri("avares://AvaloniaApplication1/Assets/Test.zip", UriKind.RelativeOrAbsolute)))
{
    stream.Seek(0, SeekOrigin.Begin);

    using (var ms = new MemoryStream())
    {
        stream.CopyTo(ms);

        var zip = new ZipArchive(ms, ZipArchiveMode.Read);
        Debug.WriteLine(zip.Entries.Count); // prints 1 as expected as we have one entry in our test zip
    }
}

yes , this is worked. there is somthing wrong in "SlicedStream". may be fix the "SlicedStream" is best way.

also use stream copyto cost a huge memory.

maxkatz6 commented 1 year ago

It feels like offset property should be adjusted here - https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Base/Platform/Internal/SlicedStream.cs#L24

landrufang commented 1 year ago

by read the source code of ZipArchive. it's seems like not only the offset property cause this

timunie commented 1 year ago

@landrufang if you have an idea how to solve the issue, we'd be happy about a PR

timunie commented 1 year ago

@maxkatz6 this is not related to xaml but how AssetLoader works

Gillibald commented 1 year ago

Y this is a bug with SlicedStream where the offset the stream is created with is not taken into account when some position is read.