1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-17 16:13:33 +08:00
Files
osu-lazer/osu.Game/Utils/ZipUtils.cs
T
Bartłomiej Dach 95233fc638 Fix replay files potentially getting misclassified as zip archives
Resolves https://github.com/ppy/osu/discussions/36107.

The replay linked in the aforementioned discussion happens to contain
the magic sequence for ZIP headers in the binary stream (50 4b 05 06)
which led to it getting classified as a ZIP with no entries and
completely bogus everything in the header at

	https://github.com/ppy/osu/blob/c8b18acd4dd1d98170a069240b6666cea3d5da07/osu.Game/Database/ImportTask.cs#L51-L56

and then finally dying of

	2025-12-23 07:26:45 [error]: [?????] Model creation of replay-osu_2040232_4828629841.osr failed.
	2025-12-23 07:26:45 [error]: System.InvalidOperationException: Sequence contains no matching element
	2025-12-23 07:26:45 [error]: at System.Linq.ThrowHelper.ThrowNoMatchException()
	2025-12-23 07:26:45 [error]: at System.Linq.Enumerable.First[TSource](IEnumerable`1 source, Func`2 predicate)
	2025-12-23 07:26:45 [error]: at osu.Game.Scoring.ScoreImporter.CreateModel(ArchiveReader archive, ImportParameters parameters)
	2025-12-23 07:26:45 [error]: at osu.Game.Database.RealmArchiveModelImporter`1.importFromArchive(ArchiveReader archive, ImportParameters parameters, CancellationToken cancellationToken)

at

	https://github.com/ppy/osu/blob/554961036e23615b35eb6b66b341b10924664c4d/osu.Game/Scoring/ScoreImporter.cs#L46
2025-12-23 08:23:23 +01:00

78 lines
2.9 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.IO;
using SharpCompress.Archives.Zip;
namespace osu.Game.Utils
{
public static class ZipUtils
{
public static bool IsZipArchive(MemoryStream stream)
{
try
{
stream.Seek(0, SeekOrigin.Begin);
using (var arc = ZipArchive.Open(stream))
{
foreach (var entry in arc.Entries)
{
using (entry.OpenEntryStream())
{
}
}
// aside from opening every zip entry not failing, we also require there to *be* at least one entry.
// if there are no entries, the best case is that it's an actual empty zip
// and as such probably useless to whatever wants to use it later.
// the worst case is that it's actually *not* a zip and instead a stream of binary
// which *accidentally* happened to contain the magic sequence of bytes for the zip header (50 4b 05 06),
// and if that's the case, then we are *misclassifying* it as a zip by returning `true` unconditionally.
return arc.Entries.Count > 0;
}
}
catch (Exception)
{
return false;
}
finally
{
stream.Seek(0, SeekOrigin.Begin);
}
}
public static bool IsZipArchive(string path)
{
if (!File.Exists(path))
return false;
try
{
using (var arc = ZipArchive.Open(path))
{
foreach (var entry in arc.Entries)
{
using (entry.OpenEntryStream())
{
}
}
// aside from opening every zip entry not failing, we also require there to *be* at least one entry.
// if there are no entries, the best case is that it's an actual empty zip
// and as such probably useless to whatever wants to use it later.
// the worst case is that it's actually *not* a zip and instead a stream of binary
// which *accidentally* happened to contain the magic sequence of bytes for the zip header (50 4b 05 06),
// and if that's the case, then we are *misclassifying* it as a zip by returning `true` unconditionally.
return arc.Entries.Count > 0;
}
}
catch (Exception)
{
return false;
}
}
}
}