Skip to content

Commit

Permalink
Expose Comment in ZipArchive and ZipArchiveEntry (dotnet#59442)
Browse files Browse the repository at this point in the history
Co-authored-by: Adam Sitnik <[email protected]>
  • Loading branch information
carlossanlop and adamsitnik authored Feb 4, 2022
1 parent 8b94165 commit 7065279
Show file tree
Hide file tree
Showing 12 changed files with 370 additions and 87 deletions.
64 changes: 64 additions & 0 deletions src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -383,5 +383,69 @@ internal static void AddEntry(ZipArchive archive, string name, string contents,
w.WriteLine(contents);
}
}

protected const string Utf8SmileyEmoji = "\ud83d\ude04";
protected const string Utf8LowerCaseOUmlautChar = "\u00F6";
protected const string Utf8CopyrightChar = "\u00A9";
protected const string AsciiFileName = "file.txt";
// The o with umlaut is a character that exists in both latin1 and utf8
protected const string Utf8AndLatin1FileName = $"{Utf8LowerCaseOUmlautChar}.txt";
// emojis only make sense in utf8
protected const string Utf8FileName = $"{Utf8SmileyEmoji}.txt";
protected static readonly string ALettersUShortMaxValueMinusOne = new string('a', ushort.MaxValue - 1);
protected static readonly string ALettersUShortMaxValue = ALettersUShortMaxValueMinusOne + 'a';
protected static readonly string ALettersUShortMaxValueMinusOneAndCopyRightChar = ALettersUShortMaxValueMinusOne + Utf8CopyrightChar;
protected static readonly string ALettersUShortMaxValueMinusOneAndTwoCopyRightChars = ALettersUShortMaxValueMinusOneAndCopyRightChar + Utf8CopyrightChar;

// Returns pairs that are returned the same way by Utf8 and Latin1
// Returns: originalComment, expectedComment
private static IEnumerable<object[]> SharedComment_Data()
{
yield return new object[] { null, string.Empty };
yield return new object[] { string.Empty, string.Empty };
yield return new object[] { "a", "a" };
yield return new object[] { Utf8LowerCaseOUmlautChar, Utf8LowerCaseOUmlautChar };
}

// Returns pairs as expected by Utf8
// Returns: originalComment, expectedComment
public static IEnumerable<object[]> Utf8Comment_Data()
{
string asciiOriginalOverMaxLength = ALettersUShortMaxValue + "aaa";

// A smiley emoji code point consists of two characters,
// meaning the whole emoji should be fully truncated
string utf8OriginalALettersAndOneEmojiDoesNotFit = ALettersUShortMaxValueMinusOne + Utf8SmileyEmoji;

// A smiley emoji code point consists of two characters,
// so it should not be truncated if it's the last character and the total length is not over the limit.
string utf8OriginalALettersAndOneEmojiFits = "aaaaa" + Utf8SmileyEmoji;

yield return new object[] { asciiOriginalOverMaxLength, ALettersUShortMaxValue };
yield return new object[] { utf8OriginalALettersAndOneEmojiDoesNotFit, ALettersUShortMaxValueMinusOne };
yield return new object[] { utf8OriginalALettersAndOneEmojiFits, utf8OriginalALettersAndOneEmojiFits };

foreach (object[] e in SharedComment_Data())
{
yield return e;
}
}

// Returns pairs as expected by Latin1
// Returns: originalComment, expectedComment
public static IEnumerable<object[]> Latin1Comment_Data()
{
// In Latin1, all characters are exactly 1 byte

string latin1ExpectedALettersAndOneOUmlaut = ALettersUShortMaxValueMinusOne + Utf8LowerCaseOUmlautChar;
string latin1OriginalALettersAndTwoOUmlauts = latin1ExpectedALettersAndOneOUmlaut + Utf8LowerCaseOUmlautChar;

yield return new object[] { latin1OriginalALettersAndTwoOUmlauts, latin1ExpectedALettersAndOneOUmlaut };

foreach (object[] e in SharedComment_Data())
{
yield return e;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ public ZipArchive(System.IO.Stream stream) { }
public ZipArchive(System.IO.Stream stream, System.IO.Compression.ZipArchiveMode mode) { }
public ZipArchive(System.IO.Stream stream, System.IO.Compression.ZipArchiveMode mode, bool leaveOpen) { }
public ZipArchive(System.IO.Stream stream, System.IO.Compression.ZipArchiveMode mode, bool leaveOpen, System.Text.Encoding? entryNameEncoding) { }
[System.Diagnostics.CodeAnalysis.AllowNull]
public string Comment { get { throw null; } set { } }
public System.Collections.ObjectModel.ReadOnlyCollection<System.IO.Compression.ZipArchiveEntry> Entries { get { throw null; } }
public System.IO.Compression.ZipArchiveMode Mode { get { throw null; } }
public System.IO.Compression.ZipArchiveEntry CreateEntry(string entryName) { throw null; }
Expand All @@ -106,6 +108,8 @@ public partial class ZipArchiveEntry
{
internal ZipArchiveEntry() { }
public System.IO.Compression.ZipArchive Archive { get { throw null; } }
[System.Diagnostics.CodeAnalysis.AllowNull]
public string Comment { get { throw null; } set { } }
public long CompressedLength { get { throw null; } }
[System.CLSCompliantAttribute(false)]
public uint Crc32 { get { throw null; } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@
<data name="EntriesInCreateMode" xml:space="preserve">
<value>Cannot access entries in Create mode.</value>
</data>
<data name="EntryNameEncodingNotSupported" xml:space="preserve">
<value>The specified entry name encoding is not supported.</value>
<data name="EntryNameAndCommentEncodingNotSupported" xml:space="preserve">
<value>The specified encoding is not supported for entry names and comments.</value>
</data>
<data name="EntryNamesTooLong" xml:space="preserve">
<value>Entry names cannot require more than 2^16 bits.</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;

namespace System.IO.Compression
Expand All @@ -27,8 +28,8 @@ public class ZipArchive : IDisposable
private uint _numberOfThisDisk; //only valid after ReadCentralDirectory
private long _expectedNumberOfEntries;
private Stream? _backingStream;
private byte[]? _archiveComment;
private Encoding? _entryNameEncoding;
private byte[] _archiveComment;
private Encoding? _entryNameAndCommentEncoding;

#if DEBUG_FORCE_ZIP64
public bool _forceZip64;
Expand Down Expand Up @@ -121,7 +122,7 @@ public ZipArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, Encoding?
if (stream == null)
throw new ArgumentNullException(nameof(stream));

EntryNameEncoding = entryNameEncoding;
EntryNameAndCommentEncoding = entryNameEncoding;
Stream? extraTempStream = null;

try
Expand Down Expand Up @@ -173,7 +174,7 @@ public ZipArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, Encoding?
_centralDirectoryStart = 0; // invalid until ReadCentralDirectory
_isDisposed = false;
_numberOfThisDisk = 0; // invalid until ReadCentralDirectory
_archiveComment = null;
_archiveComment = Array.Empty<byte>();

switch (mode)
{
Expand Down Expand Up @@ -211,6 +212,20 @@ public ZipArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, Encoding?
}
}

/// <summary>
/// Gets or sets the optional archive comment.
/// </summary>
/// <remarks>
/// The comment encoding is determined by the <c>entryNameEncoding</c> parameter of the <see cref="ZipArchive(Stream,ZipArchiveMode,bool,Encoding?)"/> constructor.
/// If the comment byte length is larger than <see cref="ushort.MaxValue"/>, it will be truncated when disposing the archive.
/// </remarks>
[AllowNull]
public string Comment
{
get => (EntryNameAndCommentEncoding ?? Encoding.UTF8).GetString(_archiveComment);
set => _archiveComment = ZipHelper.GetEncodedTruncatedBytesFromString(value, EntryNameAndCommentEncoding, ZipEndOfCentralDirectoryBlock.ZipFileCommentMaxLength, out _);
}

/// <summary>
/// The collection of entries that are currently in the ZipArchive. This may not accurately represent the actual entries that are present in the underlying file or stream.
/// </summary>
Expand Down Expand Up @@ -345,9 +360,9 @@ public void Dispose()

internal uint NumberOfThisDisk => _numberOfThisDisk;

internal Encoding? EntryNameEncoding
internal Encoding? EntryNameAndCommentEncoding
{
get { return _entryNameEncoding; }
get => _entryNameAndCommentEncoding;

private set
{
Expand All @@ -370,10 +385,10 @@ private set
(value.Equals(Encoding.BigEndianUnicode)
|| value.Equals(Encoding.Unicode)))
{
throw new ArgumentException(SR.EntryNameEncodingNotSupported, nameof(EntryNameEncoding));
throw new ArgumentException(SR.EntryNameAndCommentEncodingNotSupported, nameof(EntryNameAndCommentEncoding));
}

_entryNameEncoding = value;
_entryNameAndCommentEncoding = value;
}
}

Expand Down Expand Up @@ -547,9 +562,7 @@ private void ReadEndOfCentralDirectory()

_expectedNumberOfEntries = eocd.NumberOfEntriesInTheCentralDirectory;

// only bother saving the comment if we are in update mode
if (_mode == ZipArchiveMode.Update)
_archiveComment = eocd.ArchiveComment;
_archiveComment = eocd.ArchiveComment;

TryReadZip64EndOfCentralDirectory(eocd, eocdStart);

Expand Down
Loading

0 comments on commit 7065279

Please sign in to comment.