Skip to content

Commit 309930a

Browse files
authored
Fix wrong file names in zip files when they contain Chinese characters (#6317)
1 parent ea9c88b commit 309930a

File tree

6 files changed

+82
-11
lines changed

6 files changed

+82
-11
lines changed

Files.Launcher/MessageHandlers/FileOperationsHandler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,10 @@ public void TryCancel(string uid)
810810

811811
private void UpdateTaskbarProgress()
812812
{
813+
if (OwnerWindow == null || taskbar == null)
814+
{
815+
return;
816+
}
813817
if (operations.Any())
814818
{
815819
taskbar.SetProgressValue(OwnerWindow.Handle, (ulong)Progress, 100);

Files/Constants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,5 +159,10 @@ public static class OptionalPackages
159159
{
160160
public const string ThemesOptionalPackagesName = "49306atecsolution.ThemesforFiles";
161161
}
162+
163+
public static class Filesystem
164+
{
165+
public const int ExtendedAsciiCodePage = 437;
166+
}
162167
}
163168
}

Files/Files.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,9 @@
14891489
<PackageReference Include="SQLitePCLRaw.bundle_green">
14901490
<Version>2.0.6</Version>
14911491
</PackageReference>
1492+
<PackageReference Include="Ude.NetStandard">
1493+
<Version>1.2.0</Version>
1494+
</PackageReference>
14921495
</ItemGroup>
14931496
<ItemGroup>
14941497
<AppxManifest Include="..\Files.Package\Package.appxmanifest">

Files/Filesystem/StorageItems/ZipStorageFolder.cs

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
using Files.Helpers;
1+
using Files.Extensions;
2+
using Files.Helpers;
23
using ICSharpCode.SharpZipLib.Zip;
34
using Microsoft.Toolkit.Uwp;
45
using System;
56
using System.Collections.Generic;
67
using System.IO;
78
using System.Linq;
89
using System.Runtime.InteropServices.WindowsRuntime;
10+
using System.Text;
911
using System.Threading.Tasks;
1012
using Windows.Foundation;
1113
using Windows.Storage;
@@ -16,6 +18,60 @@ namespace Files.Filesystem.StorageItems
1618
{
1719
public sealed class ZipStorageFolder : BaseStorageFolder
1820
{
21+
public Encoding ZipEncoding { get; set; } = null;
22+
23+
static ZipStorageFolder()
24+
{
25+
// Register all supported codepages (default is UTF-X only)
26+
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
27+
// Use extended ascii so you can convert the string back to bytes
28+
ZipStrings.CodePage = Constants.Filesystem.ExtendedAsciiCodePage;
29+
}
30+
31+
public static string DecodeEntryName(ZipEntry entry, Encoding zipEncoding)
32+
{
33+
if (zipEncoding == null || entry.IsUnicodeText)
34+
{
35+
return entry.Name;
36+
}
37+
var decoded = Common.Extensions.IgnoreExceptions(() =>
38+
{
39+
var rawBytes = Encoding.GetEncoding(Constants.Filesystem.ExtendedAsciiCodePage).GetBytes(entry.Name);
40+
return zipEncoding.GetString(rawBytes);
41+
});
42+
return decoded ?? entry.Name;
43+
}
44+
45+
public static Encoding DetectFileEncoding(ZipFile zipFile)
46+
{
47+
long readEntries = 0;
48+
Ude.CharsetDetector cdet = new Ude.CharsetDetector();
49+
foreach (var entry in zipFile.OfType<ZipEntry>())
50+
{
51+
readEntries++;
52+
if (entry.IsUnicodeText)
53+
{
54+
return Encoding.UTF8;
55+
}
56+
var guessedEncoding = Common.Extensions.IgnoreExceptions(() =>
57+
{
58+
var rawBytes = Encoding.GetEncoding(Constants.Filesystem.ExtendedAsciiCodePage).GetBytes(entry.Name);
59+
cdet.Feed(rawBytes, 0, rawBytes.Length);
60+
cdet.DataEnd();
61+
if (cdet.Charset != null && cdet.Confidence >= 0.9 && (readEntries >= Math.Min(zipFile.Count, 50)))
62+
{
63+
return Encoding.GetEncoding(cdet.Charset);
64+
}
65+
return null;
66+
});
67+
if (guessedEncoding != null)
68+
{
69+
return guessedEncoding;
70+
}
71+
}
72+
return Encoding.UTF8;
73+
}
74+
1975
public ZipStorageFolder(string path, string containerPath)
2076
{
2177
Name = System.IO.Path.GetFileName(path.TrimEnd('\\', '/'));
@@ -108,7 +164,7 @@ public override IAsyncOperation<BaseStorageFolder> CreateFolderAsync(string desi
108164
zipFile.CommitUpdate();
109165

110166
var wnt = new WindowsNameTransform(ContainerPath);
111-
return new ZipStorageFolder(wnt.TransformFile(zipDesiredName), ContainerPath);
167+
return new ZipStorageFolder(wnt.TransformFile(zipDesiredName), ContainerPath) { ZipEncoding = ZipEncoding };
112168
}
113169
});
114170
}
@@ -141,17 +197,18 @@ public override IAsyncOperation<IStorageItem> GetItemAsync(string name)
141197
using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read)))
142198
{
143199
zipFile.IsStreamOwner = true;
200+
ZipEncoding ??= DetectFileEncoding(zipFile);
144201
var entry = zipFile.GetEntry(name);
145202
if (entry != null)
146203
{
147204
var wnt = new WindowsNameTransform(ContainerPath);
148205
if (entry.IsDirectory)
149206
{
150-
return new ZipStorageFolder(wnt.TransformDirectory(entry.Name), ContainerPath, entry);
207+
return new ZipStorageFolder(wnt.TransformDirectory(DecodeEntryName(entry, ZipEncoding)), ContainerPath, entry) { ZipEncoding = ZipEncoding };
151208
}
152209
else
153210
{
154-
return new ZipStorageFile(wnt.TransformFile(entry.Name), ContainerPath, entry);
211+
return new ZipStorageFile(wnt.TransformFile(DecodeEntryName(entry, ZipEncoding)), ContainerPath, entry);
155212
}
156213
}
157214
return null;
@@ -187,12 +244,13 @@ public override IAsyncOperation<IReadOnlyList<IStorageItem>> GetItemsAsync()
187244
using (ZipFile zipFile = new ZipFile(new FileStream(hFile, FileAccess.Read)))
188245
{
189246
zipFile.IsStreamOwner = true;
247+
ZipEncoding ??= DetectFileEncoding(zipFile);
190248
var wnt = new WindowsNameTransform(ContainerPath, true); // Check with Path.GetFullPath
191249
var items = new List<IStorageItem>();
192250
foreach (var entry in zipFile.OfType<ZipEntry>()) // Returns all items recursively
193251
{
194-
string winPath = System.IO.Path.GetFullPath(entry.IsDirectory ? wnt.TransformDirectory(entry.Name) : wnt.TransformFile(entry.Name));
195-
if (winPath.StartsWith(Path)) // Child of self
252+
string winPath = System.IO.Path.GetFullPath(entry.IsDirectory ? wnt.TransformDirectory(DecodeEntryName(entry, ZipEncoding)) : wnt.TransformFile(DecodeEntryName(entry, ZipEncoding)));
253+
if (winPath.StartsWith(Path.WithEnding("\\"))) // Child of self
196254
{
197255
var split = winPath.Substring(Path.Length).Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
198256
if (split.Length > 0)
@@ -202,7 +260,7 @@ public override IAsyncOperation<IReadOnlyList<IStorageItem>> GetItemsAsync()
202260
var itemPath = System.IO.Path.Combine(Path, split[0]);
203261
if (!items.Any(x => x.Path == itemPath))
204262
{
205-
items.Add(new ZipStorageFolder(itemPath, ContainerPath, entry));
263+
items.Add(new ZipStorageFolder(itemPath, ContainerPath, entry) { ZipEncoding = ZipEncoding });
206264
}
207265
}
208266
else

Files/Helpers/PathNormalization.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static string GetPathRoot(string path)
1616
{
1717
var pathAsUri = new Uri(path.Replace("\\", "/"));
1818
rootPath = pathAsUri.GetLeftPart(UriPartial.Authority);
19-
if (pathAsUri.IsFile)
19+
if (pathAsUri.IsFile && !string.IsNullOrEmpty(rootPath))
2020
{
2121
rootPath = new Uri(rootPath).LocalPath;
2222
}

Files/Helpers/ZipHelpers.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,13 @@ public static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFold
4242
}
4343

4444
var wnt = new WindowsNameTransform(destinationFolder.Path);
45+
var zipEncoding = ZipStorageFolder.DetectFileEncoding(zipFile);
4546

4647
var directories = new List<string>();
4748
try
4849
{
49-
directories.AddRange(directoryEntries.Select((item) => wnt.TransformDirectory(item.Name)));
50-
directories.AddRange(fileEntries.Select((item) => Path.GetDirectoryName(wnt.TransformFile(item.Name))));
50+
directories.AddRange(directoryEntries.Select((entry) => wnt.TransformDirectory(ZipStorageFolder.DecodeEntryName(entry, zipEncoding))));
51+
directories.AddRange(fileEntries.Select((entry) => Path.GetDirectoryName(wnt.TransformFile(ZipStorageFolder.DecodeEntryName(entry, zipEncoding)))));
5152
}
5253
catch (InvalidNameException ex)
5354
{
@@ -98,7 +99,7 @@ public static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFold
9899
continue; // TODO: support password protected archives
99100
}
100101

101-
string filePath = wnt.TransformFile(entry.Name);
102+
string filePath = wnt.TransformFile(ZipStorageFolder.DecodeEntryName(entry, zipEncoding));
102103

103104
var hFile = NativeFileOperationsHelper.CreateFileForWrite(filePath);
104105
if (hFile.IsInvalid)

0 commit comments

Comments
 (0)