diff --git a/Gemfile.lock b/Gemfile.lock index 7df9c46482..ab594aee74 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,7 +18,7 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.7.5) emoji_regex (1.0.1) - excon (0.66.0) + excon (0.67.0) faraday (0.15.4) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) @@ -27,7 +27,7 @@ GEM faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) fastimage (2.1.7) - fastlane (2.131.0) + fastlane (2.133.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) @@ -37,9 +37,9 @@ GEM dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 2.0) excon (>= 0.45.0, < 1.0.0) - faraday (~> 0.9) + faraday (< 0.16.0) faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 0.9) + faraday_middleware (< 0.16.0) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) google-api-client (>= 0.21.2, < 0.24.0) @@ -52,7 +52,7 @@ GEM multipart-post (~> 2.0.0) plist (>= 3.1.0, < 4.0.0) public_suffix (~> 2.0.0) - rubyzip (>= 1.2.2, < 2.0.0) + rubyzip (>= 1.3.0, < 2.0.0) security (= 0.1.3) simctl (~> 1.6.3) slack-notifier (>= 2.0.0, < 3.0.0) @@ -102,7 +102,7 @@ GEM memoist (0.16.0) mime-types (3.3) mime-types-data (~> 3.2015) - mime-types-data (3.2019.0904) + mime-types-data (3.2019.1009) mini_magick (4.9.5) mini_portile2 (2.4.0) multi_json (1.13.1) @@ -121,9 +121,9 @@ GEM uber (< 0.2.0) retriable (3.1.2) rouge (2.0.7) - rubyzip (1.2.4) + rubyzip (1.3.0) security (0.1.3) - signet (0.11.0) + signet (0.12.0) addressable (~> 2.3) faraday (~> 0.9) jwt (>= 1.5, < 3.0) diff --git a/osu.Android.props b/osu.Android.props index 0a831eceb2..85766665a9 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -61,7 +61,7 @@ - + diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 6da8d8cb71..e96637cae9 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportWhenClosed() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenClosed))) { try { @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDelete() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDelete))) { try { @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenImport() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImport))) { try { @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportCorruptThenImport() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportCorruptThenImport))) { try { @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestRollbackOnFailure() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestRollbackOnFailure))) { try { @@ -213,7 +213,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenImportDifferentHash() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportDifferentHash))) { try { @@ -244,7 +244,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteThenImport() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDeleteThenImport))) { try { @@ -272,7 +272,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(TestImportThenDeleteThenImportWithOnlineIDMismatch)}-{set}")) { try { @@ -306,7 +306,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportWithDuplicateBeatmapIDs() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateBeatmapIDs))) { try { @@ -392,7 +392,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWhenFileOpen() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenFileOpen))) { try { @@ -414,7 +414,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportNestedStructure() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportNestedStructure")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure))) { try { @@ -456,6 +456,60 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] + public async Task TestImportWithIgnoredDirectoryInArchive() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithIgnoredDirectoryInArchive))) + { + try + { + var osu = loadOsu(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + string dataFolder = Path.Combine(extractedFolder, "actual_data"); + string resourceForkFolder = Path.Combine(extractedFolder, "__MACOSX"); + string resourceForkFilePath = Path.Combine(resourceForkFolder, ".extracted"); + + Directory.CreateDirectory(dataFolder); + Directory.CreateDirectory(resourceForkFolder); + + using (var resourceForkFile = File.CreateText(resourceForkFilePath)) + { + resourceForkFile.WriteLine("adding content so that it's not empty"); + } + + try + { + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(dataFolder); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + var imported = await osu.Dependencies.Get().Import(temp); + + ensureLoaded(osu); + + Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored"); + Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder"); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } + } + public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null) { var temp = path ?? TestResources.GetTestBeatmapForImport(); diff --git a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs index b582ca0a6f..25517ad615 100644 --- a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using System.Text; using NUnit.Framework; @@ -14,9 +15,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestReadLineByLine() { - const string contents = @"line 1 -line 2 -line 3"; + const string contents = "line 1\rline 2\nline 3"; using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var bufferedReader = new LineBufferedReader(stream)) @@ -31,9 +30,7 @@ line 3"; [Test] public void TestPeekLineOnce() { - const string contents = @"line 1 -peek this -line 3"; + const string contents = "line 1\r\npeek this\nline 3"; using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var bufferedReader = new LineBufferedReader(stream)) @@ -49,9 +46,7 @@ line 3"; [Test] public void TestPeekLineMultipleTimes() { - const string contents = @"peek this once -line 2 -peek this a lot"; + const string contents = "peek this once\nline 2\rpeek this a lot"; using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var bufferedReader = new LineBufferedReader(stream)) @@ -70,8 +65,7 @@ peek this a lot"; [Test] public void TestPeekLineAtEndOfStream() { - const string contents = @"first line -second line"; + const string contents = "first line\r\nsecond line"; using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var bufferedReader = new LineBufferedReader(stream)) @@ -100,8 +94,7 @@ second line"; [Test] public void TestReadToEndNoPeeks() { - const string contents = @"first line -second line"; + const string contents = "first line\r\nsecond line"; using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var bufferedReader = new LineBufferedReader(stream)) @@ -113,20 +106,19 @@ second line"; [Test] public void TestReadToEndAfterReadsAndPeeks() { - const string contents = @"this line is gone -this one shouldn't be -these ones -definitely not"; + const string contents = "this line is gone\rthis one shouldn't be\r\nthese ones\ndefinitely not"; using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var bufferedReader = new LineBufferedReader(stream)) { Assert.AreEqual("this line is gone", bufferedReader.ReadLine()); Assert.AreEqual("this one shouldn't be", bufferedReader.PeekLine()); - const string ending = @"this one shouldn't be -these ones -definitely not"; - Assert.AreEqual(ending, bufferedReader.ReadToEnd()); + + var endingLines = bufferedReader.ReadToEnd().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + Assert.AreEqual(3, endingLines.Length); + Assert.AreEqual("this one shouldn't be", endingLines[0]); + Assert.AreEqual("these ones", endingLines[1]); + Assert.AreEqual("definitely not", endingLines[2]); } } } diff --git a/osu.Game/IO/Archives/ZipArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs index d934ac54c4..9033e7529d 100644 --- a/osu.Game/IO/Archives/ZipArchiveReader.cs +++ b/osu.Game/IO/Archives/ZipArchiveReader.cs @@ -1,15 +1,30 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.IO; using System.Linq; using SharpCompress.Archives.Zip; +using SharpCompress.Common; namespace osu.Game.IO.Archives { public sealed class ZipArchiveReader : ArchiveReader { + /// + /// List of substrings that indicate a file should be ignored during the import process + /// (usually due to representing no useful data and being autogenerated by the OS). + /// + private static readonly string[] filename_ignore_list = + { + // Mac-specific + "__MACOSX", + ".DS_Store", + // Windows-specific + "Thumbs.db" + }; + private readonly Stream archiveStream; private readonly ZipArchive archive; @@ -43,7 +58,9 @@ namespace osu.Game.IO.Archives archiveStream.Dispose(); } - public override IEnumerable Filenames => archive.Entries.Select(e => e.Key).ToArray(); + private static bool canBeIgnored(IEntry entry) => filename_ignore_list.Any(ignoredName => entry.Key.IndexOf(ignoredName, StringComparison.OrdinalIgnoreCase) >= 0); + + public override IEnumerable Filenames => archive.Entries.Where(e => !canBeIgnored(e)).Select(e => e.Key).ToArray(); public override Stream GetUnderlyingStream() => archiveStream; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 6c5f64ed6c..055ad89b6d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -66,11 +66,7 @@ namespace osu.Game.Screens.Select /// protected readonly Container FooterPanels; - protected override BackgroundScreen CreateBackground() - { - var background = new BackgroundScreenBeatmap(); - return background; - } + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(); protected readonly BeatmapCarousel Carousel; private readonly BeatmapInfoWedge beatmapInfoWedge; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c2888c8e21..ab7c40116b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 8ad7a58e36..925a217a13 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -117,7 +117,7 @@ - +