Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Expose the ExternalAttributes field in ZipArchive #18565

Merged
merged 3 commits into from
Apr 19, 2017
Merged

Expose the ExternalAttributes field in ZipArchive #18565

merged 3 commits into from
Apr 19, 2017

Conversation

ianhays
Copy link
Contributor

@ianhays ianhays commented Apr 18, 2017

The ExternalFileAttributes field of a ZipArchiveEntry is currently hidden within the implementation and not accessible. This has been fine in the Windows world since it's not used but it is used by Unix's zip/unzip programs to store file permissions of an entry. As it currently stands, there is no way to carry Unix RWX permissions within a zip, so at unzip time the unzipper has to guess at permissions and manually chmod the entries. This PR simply exposes the raw field so that it may be used in any circumstance that requires access to the field. Tests are added to ensure that this field is set, read, and roundtrips properly with zips created from other common programs which use the field.

Note: Unix unzip doesn't apply permissions in the external file attributes field of a zip produced on Windows. So if you modify the externalfileattributes of a zip on Windows, unzip will use the defaults instead of what is set in the field. This isn't easily resolvable on our side since the platformmadeby byte determines a number of other things that we don't want to mess with (e.g. file name validity), so it would be unwise to lie about the platform on which the zip was written.

resolves https:/dotnet/corefx/issues/17067

cc: @eerhardt @danmosemsft @stephentoub

The ExternalFileAttributes field of a ZipArchiveEntry is currently hidden within the implementation and not accessible. This has been fine in the Windows world since it's not used but it is used by Unix's zip/unzip programs to store file permissions of an entry. As it currently stands, there is no way to carry Unix RWX permissions within a zip, so at unzip time the unzipper has to guess at permissions and manually chmod the entries. This PR simply exposes the raw field so that it may be used in any circumstance that requires access to the field. Tests are added to ensure that this field is set, read, and roundtrips properly with zips created from other common programs which use the field.
@ianhays ianhays added api-approved API was approved in API review, it can be implemented area-System.IO.Compression labels Apr 18, 2017
@ianhays ianhays added this to the 2.0.0 milestone Apr 18, 2017
@ianhays ianhays self-assigned this Apr 18, 2017
@@ -59,7 +59,7 @@
<Version>2.5.0</Version>
</PackageReference>
<PackageReference Include="System.IO.Compression.TestData">
<Version>1.0.4-prerelease</Version>
<Version>1.0.5-prerelease</Version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you just manually upload this data when you need to?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. The TestData package doesn't need to be changed very often so it's not too much of a hassle.

@@ -39,6 +39,7 @@ public partial class ZipArchiveEntry
private bool _currentlyOpenForWrite;
private bool _everOpenedForWrite;
private Stream _outstandingWriteStream;
private uint _externalFileAttr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it still correct to use uint internally? Does it matter?
It's a bit odd to use it and then test with int.MaxValue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't really matter. We could use an int internally instead, I just like the uint since you don't have to worry about bit carrying when doing shifts and it's already being using in the ZipCentralDirectoryFileHeader reader. Both could be changed to ints easily though.

@@ -527,7 +543,7 @@ internal void WriteCentralDirectoryFileHeader()
writer.Write(_fileComment != null ? (ushort)_fileComment.Length : (ushort)0); // file comment length
writer.Write((ushort)0); // disk number start
writer.Write((ushort)0); // internal file attributes
writer.Write((uint)0); // external file attributes
writer.Write(_externalFileAttr); // external file attributes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this line actually getting hit by your tests? Seems you're just reading zips you already made, and testing the set/get pairs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not, really. Testing that the zips written work correctly with other programs requires manual testing (which FWIW I did).

As @eerhardt mentioned below, I can add a test to do a more complete roundtripping test to hit this line.

[InlineData(int.MaxValue)]
[InlineData(int.MinValue)]
[InlineData(0)]
public static async Task RoundTrips_UnixFilePermissions(int expectedAttr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we should have a test that actually writes the ExternalAttributes to a file, and then reads the file in and makes sure the ExternalAttributes are preserved in a full "round-trip".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I'll add one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added. There isn't any reason to write the stream to a file, so I closed the ZipArchive then reopened it to ensure the attribute was written to the stream correctly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Just reading it back out of the stream works for me. Thanks.

@@ -45,8 +49,11 @@
<Link>Common\System\IO\Compression\ZipTestHelper.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp'">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this new property not going to be supported in netstandard?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

netstandard configuration means you run on netstandard. Adding the parameter is fine. I forgot to ask why you have to add a netcoreapp configuration here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would have to be added to full .net to be a part of netstandard, yes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but this has a netstandard configuration which indicates it works on top of netstandard. It can expose any API it wants. I don't think you need any new configuraiotn.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disregard the above --this is a test configuration. I believe you did it right. My bad.

@@ -4,6 +4,8 @@
<BuildConfigurations>
netstandard-Unix;
netstandard-Windows_NT;
netcoreapp-Unix;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean we will build and run all the tests twice (with the exception of the newly added tests, which will only run once on netcoreapp)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not certain. As netstandard should be a subset of netcoreapp, it wouldn't make much sense to run a netstandard build alongside a netcoreapp build. My assumption is that when running tests against netcoreapp via build-tests.cmd, only the most specific Configuration is ran e.g. netcoreapp if available otherwise netstandard, or netfx where available otherwise netstandard. @karajas probably knows more.

Regardless, the important bit is that the new tests will run only on netcoreapp.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ianhays is correct. We have https:/dotnet/corefx/issues/15667 to try and run explicitly the netstandard test configurations as well but in general those should be included in the netcoreapp configurations.

Copy link
Member

@stephentoub stephentoub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than Eric's feedback, LGTM.

Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pushing forward with this @ianhays. This gets us closer to where we need to be to fully support non-Windows scenarios.

[InlineData(int.MaxValue)]
[InlineData(int.MinValue)]
[InlineData(0)]
public static async Task RoundTrips_UnixFilePermissions(int expectedAttr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Just reading it back out of the stream works for me. Thanks.

@karelz karelz removed the api-approved API was approved in API review, it can be implemented label Apr 18, 2017
@ianhays
Copy link
Contributor Author

ianhays commented Apr 19, 2017

Glad to help @eerhardt!

@theladyjaye
Copy link

ExternalAttributes appears to be available in .NET Core 2.x but it is not available in .NET Standard 2.x?

.NET Standard:
https://docs.microsoft.com/en-us/dotnet/api/system.io.compression.ziparchiveentry?view=netstandard-2.0

.NET Core
https://docs.microsoft.com/en-us/dotnet/api/system.io.compression.ziparchiveentry?view=netcore-2.0

@weshaggard
Copy link
Member

@aventurella that is correct. This is a new API which is added to .NET Core first and will be considered in a future version of .NET Standard. The standard versions at a slower rate then .NET Core.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Compression.ZipArchive: Expose ExternalFileAttributes for Entries
8 participants