2
* Licensed to the Apache Software Foundation (ASF) under one
3
* or more contributor license agreements. See the NOTICE file
4
* distributed with this work for additional information
5
* regarding copyright ownership. The ASF licenses this file
6
* to you under the Apache License, Version 2.0 (the
7
* "License"); you may not use this file except in compliance
8
* with the License. You may obtain a copy of the License at
10
* http://www.apache.org/licenses/LICENSE-2.0
12
* Unless required by applicable law or agreed to in writing,
13
* software distributed under the License is distributed on an
14
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
* KIND, either express or implied. See the License for the
16
* specific language governing permissions and limitations
19
package org.apache.commons.compress.archivers.zip;
21
import java.io.Serializable;
22
import java.math.BigInteger;
23
import java.util.zip.ZipException;
25
import static org.apache.commons.compress.archivers.zip.ZipUtil.reverse;
26
import static org.apache.commons.compress.archivers.zip.ZipUtil.signedByteToUnsignedInt;
27
import static org.apache.commons.compress.archivers.zip.ZipUtil.unsignedIntToSignedByte;
30
* An extra field that stores UNIX UID/GID data (owner & group ownership) for a given
31
* zip entry. We're using the field definition given in Info-Zip's source archive:
32
* zip-3.0.tar.gz/proginfo/extrafld.txt
35
* Value Size Description
36
* ----- ---- -----------
37
* 0x7875 Short tag for this extra block type ("ux")
38
* TSize Short total data size for this block
39
* Version 1 byte version of this extra field, currently 1
40
* UIDSize 1 byte Size of UID field
41
* UID Variable UID for this entry (little endian)
42
* GIDSize 1 byte Size of GID field
43
* GID Variable GID for this entry (little endian)
47
public class X7875_NewUnix implements ZipExtraField, Cloneable, Serializable {
48
private static final ZipShort HEADER_ID = new ZipShort(0x7875);
49
private static final BigInteger ONE_THOUSAND = BigInteger.valueOf(1000);
50
private static final long serialVersionUID = 1L;
52
private int version = 1; // always '1' according to current info-zip spec.
54
// BigInteger helps us with little-endian / big-endian conversions.
55
// (thanks to BigInteger.toByteArray() and a reverse() method we created).
56
// Also, the spec theoretically allows UID/GID up to 255 bytes long!
58
// NOTE: equals() and hashCode() currently assume these can never be null.
59
private BigInteger uid;
60
private BigInteger gid;
63
* Constructor for X7875_NewUnix.
65
public X7875_NewUnix() {
72
* @return the value for the header id for this extrafield
74
public ZipShort getHeaderId() {
79
* Gets the UID as a long. UID is typically a 32 bit unsigned
80
* value on most UNIX systems, so we return a long to avoid
81
* integer overflow into the negatives in case values above
82
* and including 2^31 are being used.
84
* @return the UID value.
86
public long getUID() { return ZipUtil.bigToLong(uid); }
89
* Gets the GID as a long. GID is typically a 32 bit unsigned
90
* value on most UNIX systems, so we return a long to avoid
91
* integer overflow into the negatives in case values above
92
* and including 2^31 are being used.
94
* @return the GID value.
96
public long getGID() { return ZipUtil.bigToLong(gid); }
101
* @param l UID value to set on this extra field.
103
public void setUID(long l) {
104
this.uid = ZipUtil.longToBig(l);
110
* @param l GID value to set on this extra field.
112
public void setGID(long l) {
113
this.gid = ZipUtil.longToBig(l);
117
* Length of the extra field in the local file data - without
118
* Header-ID or length specifier.
120
* @return a <code>ZipShort</code> for the length of the data of this extra field
122
public ZipShort getLocalFileDataLength() {
123
int uidSize = trimLeadingZeroesForceMinLength(uid.toByteArray()).length;
124
int gidSize = trimLeadingZeroesForceMinLength(gid.toByteArray()).length;
126
// The 3 comes from: version=1 + uidsize=1 + gidsize=1
127
return new ZipShort(3 + uidSize + gidSize);
131
* Length of the extra field in the central directory data - without
132
* Header-ID or length specifier.
134
* @return a <code>ZipShort</code> for the length of the data of this extra field
136
public ZipShort getCentralDirectoryLength() {
137
return getLocalFileDataLength(); // No different than local version.
141
* The actual data to put into local file data - without Header-ID
142
* or length specifier.
144
* @return get the data
146
public byte[] getLocalFileDataData() {
147
byte[] uidBytes = uid.toByteArray();
148
byte[] gidBytes = gid.toByteArray();
150
// BigInteger might prepend a leading-zero to force a positive representation
151
// (e.g., so that the sign-bit is set to zero). We need to remove that
152
// before sending the number over the wire.
153
uidBytes = trimLeadingZeroesForceMinLength(uidBytes);
154
gidBytes = trimLeadingZeroesForceMinLength(gidBytes);
156
// Couldn't bring myself to just call getLocalFileDataLength() when we've
157
// already got the arrays right here. Yeah, yeah, I know, premature
158
// optimization is the root of all...
160
// The 3 comes from: version=1 + uidsize=1 + gidsize=1
161
byte[] data = new byte[3 + uidBytes.length + gidBytes.length];
163
// reverse() switches byte array from big-endian to little-endian.
168
data[pos++] = unsignedIntToSignedByte(version);
169
data[pos++] = unsignedIntToSignedByte(uidBytes.length);
170
System.arraycopy(uidBytes, 0, data, pos, uidBytes.length);
171
pos += uidBytes.length;
172
data[pos++] = unsignedIntToSignedByte(gidBytes.length);
173
System.arraycopy(gidBytes, 0, data, pos, gidBytes.length);
178
* The actual data to put into central directory data - without Header-ID
179
* or length specifier.
181
* @return get the data
183
public byte[] getCentralDirectoryData() {
184
return getLocalFileDataData();
188
* Populate data from this array as if it was in local file data.
190
* @param data an array of bytes
191
* @param offset the start offset
192
* @param length the number of bytes in the array from offset
193
* @throws java.util.zip.ZipException on error
195
public void parseFromLocalFileData(
196
byte[] data, int offset, int length
197
) throws ZipException {
199
this.version = signedByteToUnsignedInt(data[offset++]);
200
int uidSize = signedByteToUnsignedInt(data[offset++]);
201
byte[] uidBytes = new byte[uidSize];
202
System.arraycopy(data, offset, uidBytes, 0, uidSize);
204
this.uid = new BigInteger(1, reverse(uidBytes)); // sign-bit forced positive
206
int gidSize = signedByteToUnsignedInt(data[offset++]);
207
byte[] gidBytes = new byte[gidSize];
208
System.arraycopy(data, offset, gidBytes, 0, gidSize);
209
this.gid = new BigInteger(1, reverse(gidBytes)); // sign-bit forced positive
213
* Doesn't do anything special since this class always uses the
214
* same data in central directory and local file data.
216
public void parseFromCentralDirectoryData(
217
byte[] buffer, int offset, int length
218
) throws ZipException {
220
parseFromLocalFileData(buffer, offset, length);
224
* Reset state back to newly constructed state. Helps us make sure
225
* parse() calls always generate clean results.
227
private void reset() {
228
// Typical UID/GID of the first non-root user created on a unix system.
234
* Returns a String representation of this class useful for
235
* debugging purposes.
237
* @return A String representation of this class useful for
238
* debugging purposes.
241
public String toString() {
242
return "0x7875 Zip Extra Field: UID=" + uid + " GID=" + gid;
246
public Object clone() throws CloneNotSupportedException {
247
return super.clone();
251
public boolean equals(Object o) {
252
if (o instanceof X7875_NewUnix) {
253
X7875_NewUnix xf = (X7875_NewUnix) o;
254
// We assume uid and gid can never be null.
255
return version == xf.version && uid.equals(xf.uid) && gid.equals(xf.gid);
261
public int hashCode() {
262
int hc = (-1234567 * version);
263
// Since most UID's and GID's are below 65,536, this is (hopefully!)
264
// a nice way to make sure typical UID and GID values impact the hash
265
// as much as possible.
266
hc ^= Integer.rotateLeft(uid.hashCode(), 16);
267
hc ^= gid.hashCode();
272
* Not really for external usage, but marked "package" visibility
273
* to help us JUnit it. Trims a byte array of leading zeroes while
274
* also enforcing a minimum length, and thus it really trims AND pads
277
* @param array byte[] array to trim & pad.
278
* @return trimmed & padded byte[] array.
280
static byte[] trimLeadingZeroesForceMinLength(byte[] array) {
286
for (byte b : array) {
296
I agonized over my choice of MIN_LENGTH=1. Here's the situation:
297
InfoZip (the tool I am using to test interop) always sets these
298
to length=4. And so a UID of 0 (typically root) for example is
299
encoded as {4,0,0,0,0} (len=4, 32 bits of zero), when it could just
300
as easily be encoded as {1,0} (len=1, 8 bits of zero) according to
303
In the end I decided on MIN_LENGTH=1 for four reasons:
305
1.) We are adhering to the spec as far as I can tell, and so
306
a consumer that cannot parse this is broken.
308
2.) Fundamentally, zip files are about shrinking things, so
309
let's save a few bytes per entry while we can.
311
3.) Of all the people creating zip files using commons-
312
compress, how many care about UNIX UID/GID attributes
313
of the files they store? (e.g., I am probably thinking
314
way too hard about this and no one cares!)
316
4.) InfoZip's tool, even though it carefully stores every UID/GID
317
for every file zipped on a unix machine (by default) currently
318
appears unable to ever restore UID/GID.
319
unzip -X has no effect on my machine, even when run as root!!!!
321
And thus it is decided: MIN_LENGTH=1.
323
If anyone runs into interop problems from this, feel free to set
324
it to MIN_LENGTH=4 at some future time, and then we will behave
325
exactly like InfoZip (requires changes to unit tests, though).
327
And I am sorry that the time you spent reading this comment is now
328
gone and you can never have it back.
331
final int MIN_LENGTH = 1;
333
byte[] trimmedArray = new byte[Math.max(MIN_LENGTH, array.length - pos)];
334
int startPos = trimmedArray.length - (array.length - pos);
335
System.arraycopy(array, pos, trimmedArray, startPos, trimmedArray.length - startPos);