View Javadoc
1   /*
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
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
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
17   * under the License.
18   */
19  package org.apache.commons.compress.archivers.zip;
20  
21  import static org.apache.commons.compress.archivers.zip.ZipUtil.reverse;
22  import static org.apache.commons.compress.archivers.zip.ZipUtil.signedByteToUnsignedInt;
23  import static org.apache.commons.compress.archivers.zip.ZipUtil.unsignedIntToSignedByte;
24  
25  import java.io.Serializable;
26  import java.math.BigInteger;
27  import java.util.Arrays;
28  import java.util.zip.ZipException;
29  
30  import org.apache.commons.compress.utils.ByteUtils;
31  
32  /**
33   * An extra field that stores UNIX UID/GID data (owner & group ownership) for a given ZIP entry. We're using the field definition given in Info-Zip's source
34   * archive: zip-3.0.tar.gz/proginfo/extrafld.txt
35   *
36   * <pre>
37   * Local-header version:
38   *
39   * Value         Size        Description
40   * -----         ----        -----------
41   * 0x7875        Short       tag for this extra block type ("ux")
42   * TSize         Short       total data size for this block
43   * Version       1 byte      version of this extra field, currently 1
44   * UIDSize       1 byte      Size of UID field
45   * UID           Variable    UID for this entry (little-endian)
46   * GIDSize       1 byte      Size of GID field
47   * GID           Variable    GID for this entry (little-endian)
48   *
49   * Central-header version:
50   *
51   * Value         Size        Description
52   * -----         ----        -----------
53   * 0x7855        Short       tag for this extra block type ("Ux")
54   * TSize         Short       total data size for this block (0)
55   * </pre>
56   *
57   * @since 1.5
58   */
59  public class X7875_NewUnix implements ZipExtraField, Cloneable, Serializable {
60      static final ZipShort HEADER_ID = new ZipShort(0x7875);
61      private static final ZipShort ZERO = new ZipShort(0);
62      private static final BigInteger ONE_THOUSAND = BigInteger.valueOf(1000);
63      private static final long serialVersionUID = 1L;
64  
65      /**
66       * Not really for external usage, but marked "package" visibility to help us JUnit it. Trims a byte array of leading zeroes while also enforcing a minimum
67       * length, and thus it really trims AND pads at the same time.
68       *
69       * @param array byte[] array to trim & pad.
70       * @return trimmed & padded byte[] array.
71       */
72      static byte[] trimLeadingZeroesForceMinLength(final byte[] array) {
73          if (array == null) {
74              return null;
75          }
76  
77          int pos = 0;
78          for (final byte b : array) {
79              if (b != 0) {
80                  break;
81              }
82              pos++;
83          }
84  
85          /*
86           *
87           * I agonized over my choice of MIN_LENGTH=1. Here's the situation: InfoZip (the tool I am using to test interop) always sets these to length=4. And so
88           * a UID of 0 (typically root) for example is encoded as {4,0,0,0,0} (len=4, 32 bits of zero), when it could just as easily be encoded as {1,0} (len=1,
89           * 8 bits of zero) according to the spec.
90           *
91           * In the end I decided on MIN_LENGTH=1 for four reasons:
92           *
93           * 1.) We are adhering to the spec as far as I can tell, and so a consumer that cannot parse this is broken.
94           *
95           * 2.) Fundamentally, ZIP files are about shrinking things, so let's save a few bytes per entry while we can.
96           *
97           * 3.) Of all the people creating ZIP files using commons- compress, how many care about UNIX UID/GID attributes of the files they store? (e.g., I am
98           * probably thinking way too hard about this and no one cares!)
99           *
100          * 4.) InfoZip's tool, even though it carefully stores every UID/GID for every file zipped on a Unix machine (by default) currently appears unable to
101          * ever restore UID/GID. unzip -X has no effect on my machine, even when run as root!!!!
102          *
103          * And thus it is decided: MIN_LENGTH=1.
104          *
105          * If anyone runs into interop problems from this, feel free to set it to MIN_LENGTH=4 at some future time, and then we will behave exactly like InfoZip
106          * (requires changes to unit tests, though).
107          *
108          * And I am sorry that the time you spent reading this comment is now gone, and you can never have it back.
109          */
110         final int MIN_LENGTH = 1;
111 
112         final byte[] trimmedArray = new byte[Math.max(MIN_LENGTH, array.length - pos)];
113         final int startPos = trimmedArray.length - (array.length - pos);
114         System.arraycopy(array, pos, trimmedArray, startPos, trimmedArray.length - startPos);
115         return trimmedArray;
116     }
117 
118     private int version = 1; // always '1' according to current info-zip spec.
119     // BigInteger helps us with little-endian / big-endian conversions.
120     // (thanks to BigInteger.toByteArray() and a reverse() method we created).
121     // Also, the spec theoretically allows UID/GID up to 255 bytes long!
122     //
123     // NOTE: equals() and hashCode() currently assume these can never be null.
124     private BigInteger uid;
125 
126     private BigInteger gid;
127 
128     /**
129      * Constructor for X7875_NewUnix.
130      */
131     public X7875_NewUnix() {
132         reset();
133     }
134 
135     @Override
136     public Object clone() throws CloneNotSupportedException {
137         return super.clone();
138     }
139 
140     @Override
141     public boolean equals(final Object o) {
142         if (o instanceof X7875_NewUnix) {
143             final X7875_NewUnix xf = (X7875_NewUnix) o;
144             // We assume uid and gid can never be null.
145             return version == xf.version && uid.equals(xf.uid) && gid.equals(xf.gid);
146         }
147         return false;
148     }
149 
150     /**
151      * The actual data to put into central directory data - without Header-ID or length specifier.
152      *
153      * @return get the data
154      */
155     @Override
156     public byte[] getCentralDirectoryData() {
157         return ByteUtils.EMPTY_BYTE_ARRAY;
158     }
159 
160     /**
161      * Length of the extra field in the central directory data - without Header-ID or length specifier.
162      *
163      * @return a {@code ZipShort} for the length of the data of this extra field
164      */
165     @Override
166     public ZipShort getCentralDirectoryLength() {
167         return ZERO;
168     }
169 
170     /**
171      * Gets the GID as a long. GID is typically a 32 bit unsigned value on most UNIX systems, so we return a long to avoid integer overflow into the negatives
172      * in case values above and including 2^31 are being used.
173      *
174      * @return the GID value.
175      */
176     public long getGID() {
177         return ZipUtil.bigToLong(gid);
178     }
179 
180     /**
181      * The Header-ID.
182      *
183      * @return the value for the header id for this extrafield
184      */
185     @Override
186     public ZipShort getHeaderId() {
187         return HEADER_ID;
188     }
189 
190     /**
191      * The actual data to put into local file data - without Header-ID or length specifier.
192      *
193      * @return get the data
194      */
195     @Override
196     public byte[] getLocalFileDataData() {
197         byte[] uidBytes = uid.toByteArray();
198         byte[] gidBytes = gid.toByteArray();
199 
200         // BigInteger might prepend a leading-zero to force a positive representation
201         // (e.g., so that the sign-bit is set to zero). We need to remove that
202         // before sending the number over the wire.
203         uidBytes = trimLeadingZeroesForceMinLength(uidBytes);
204         final int uidBytesLen = uidBytes != null ? uidBytes.length : 0;
205         gidBytes = trimLeadingZeroesForceMinLength(gidBytes);
206         final int gidBytesLen = gidBytes != null ? gidBytes.length : 0;
207 
208         // Couldn't bring myself to just call getLocalFileDataLength() when we've
209         // already got the arrays right here. Yeah, yeah, I know, premature
210         // optimization is the root of all...
211         //
212         // The 3 comes from: version=1 + uidsize=1 + gidsize=1
213         final byte[] data = new byte[3 + uidBytesLen + gidBytesLen];
214 
215         // reverse() switches byte array from big-endian to little-endian.
216         if (uidBytes != null) {
217             reverse(uidBytes);
218         }
219         if (gidBytes != null) {
220             reverse(gidBytes);
221         }
222 
223         int pos = 0;
224         data[pos++] = unsignedIntToSignedByte(version);
225         data[pos++] = unsignedIntToSignedByte(uidBytesLen);
226         if (uidBytes != null) {
227             System.arraycopy(uidBytes, 0, data, pos, uidBytesLen);
228         }
229         pos += uidBytesLen;
230         data[pos++] = unsignedIntToSignedByte(gidBytesLen);
231         if (gidBytes != null) {
232             System.arraycopy(gidBytes, 0, data, pos, gidBytesLen);
233         }
234         return data;
235     }
236 
237     /**
238      * Length of the extra field in the local file data - without Header-ID or length specifier.
239      *
240      * @return a {@code ZipShort} for the length of the data of this extra field
241      */
242     @Override
243     public ZipShort getLocalFileDataLength() {
244         byte[] b = trimLeadingZeroesForceMinLength(uid.toByteArray());
245         final int uidSize = b == null ? 0 : b.length;
246         b = trimLeadingZeroesForceMinLength(gid.toByteArray());
247         final int gidSize = b == null ? 0 : b.length;
248 
249         // The 3 comes from: version=1 + uidsize=1 + gidsize=1
250         return new ZipShort(3 + uidSize + gidSize);
251     }
252 
253     /**
254      * Gets the UID as a long. UID is typically a 32 bit unsigned value on most UNIX systems, so we return a long to avoid integer overflow into the negatives
255      * in case values above and including 2^31 are being used.
256      *
257      * @return the UID value.
258      */
259     public long getUID() {
260         return ZipUtil.bigToLong(uid);
261     }
262 
263     @Override
264     public int hashCode() {
265         int hc = -1234567 * version;
266         // Since most UIDs and GIDs are below 65,536, this is (hopefully!)
267         // a nice way to make sure typical UID and GID values impact the hash
268         // as much as possible.
269         hc ^= Integer.rotateLeft(uid.hashCode(), 16);
270         hc ^= gid.hashCode();
271         return hc;
272     }
273 
274     /**
275      * Doesn't do anything since this class doesn't store anything inside the central directory.
276      */
277     @Override
278     public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
279     }
280 
281     /**
282      * Populate data from this array as if it was in local file data.
283      *
284      * @param data   an array of bytes
285      * @param offset the start offset
286      * @param length the number of bytes in the array from offset
287      * @throws java.util.zip.ZipException on error
288      */
289     @Override
290     public void parseFromLocalFileData(final byte[] data, int offset, final int length) throws ZipException {
291         reset();
292         if (length < 3) {
293             throw new ZipException("X7875_NewUnix length is too short, only " + length + " bytes");
294         }
295         this.version = signedByteToUnsignedInt(data[offset++]);
296         final int uidSize = signedByteToUnsignedInt(data[offset++]);
297         if (uidSize + 3 > length) {
298             throw new ZipException("X7875_NewUnix invalid: uidSize " + uidSize + " doesn't fit into " + length + " bytes");
299         }
300         final byte[] uidBytes = Arrays.copyOfRange(data, offset, offset + uidSize);
301         offset += uidSize;
302         this.uid = new BigInteger(1, reverse(uidBytes)); // sign-bit forced positive
303 
304         final int gidSize = signedByteToUnsignedInt(data[offset++]);
305         if (uidSize + 3 + gidSize > length) {
306             throw new ZipException("X7875_NewUnix invalid: gidSize " + gidSize + " doesn't fit into " + length + " bytes");
307         }
308         final byte[] gidBytes = Arrays.copyOfRange(data, offset, offset + gidSize);
309         this.gid = new BigInteger(1, reverse(gidBytes)); // sign-bit forced positive
310     }
311 
312     /**
313      * Reset state back to newly constructed state. Helps us make sure parse() calls always generate clean results.
314      */
315     private void reset() {
316         // Typical UID/GID of the first non-root user created on a Unix system.
317         uid = ONE_THOUSAND;
318         gid = ONE_THOUSAND;
319     }
320 
321     /**
322      * Sets the GID.
323      *
324      * @param l GID value to set on this extra field.
325      */
326     public void setGID(final long l) {
327         this.gid = ZipUtil.longToBig(l);
328     }
329 
330     /**
331      * Sets the UID.
332      *
333      * @param l UID value to set on this extra field.
334      */
335     public void setUID(final long l) {
336         this.uid = ZipUtil.longToBig(l);
337     }
338 
339     /**
340      * Returns a String representation of this class useful for debugging purposes.
341      *
342      * @return A String representation of this class useful for debugging purposes.
343      */
344     @Override
345     public String toString() {
346         return "0x7875 Zip Extra Field: UID=" + uid + " GID=" + gid;
347     }
348 }