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   *   https://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.unsignedIntToSignedByte;
23  
24  import java.io.Serializable;
25  import java.math.BigInteger;
26  import java.util.Arrays;
27  import java.util.zip.ZipException;
28  
29  import org.apache.commons.compress.utils.ByteUtils;
30  import org.apache.commons.lang3.ArrayUtils;
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           * 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
87           * 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,
88           * 8 bits of zero) according to the spec.
89           *
90           * In the end I decided on MIN_LENGTH=1 for four reasons:
91           *
92           * 1.) We are adhering to the spec as far as I can tell, and so a consumer that cannot parse this is broken.
93           *
94           * 2.) Fundamentally, ZIP files are about shrinking things, so let's save a few bytes per entry while we can.
95           *
96           * 3.) Of all the people creating ZIP files using commons- compress, how many care about Unix UID/GID attributes of the files they store? (for example,
97           * I am probably thinking way too hard about this and no one cares!)
98           *
99           * 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
100          * ever restore UID/GID. unzip -X has no effect on my machine, even when run as root!!!!
101          *
102          * And thus it is decided: MIN_LENGTH=1.
103          *
104          * 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
105          * (requires changes to unit tests, though).
106          *
107          * And I am sorry that the time you spent reading this comment is now gone, and you can never have it back.
108          */
109         final int MIN_LENGTH = 1;
110 
111         final byte[] trimmedArray = new byte[Math.max(MIN_LENGTH, array.length - pos)];
112         final int startPos = trimmedArray.length - (array.length - pos);
113         System.arraycopy(array, pos, trimmedArray, startPos, trimmedArray.length - startPos);
114         return trimmedArray;
115     }
116 
117     private int version = 1; // always '1' according to current info-zip spec.
118     // BigInteger helps us with little-endian / big-endian conversions.
119     // (thanks to BigInteger.toByteArray() and a reverse() method we created).
120     // Also, the spec theoretically allows UID/GID up to 255 bytes long!
121     //
122     // NOTE: equals() and hashCode() currently assume these can never be null.
123     private BigInteger uid;
124 
125     private BigInteger gid;
126 
127     /**
128      * Constructor for X7875_NewUnix.
129      */
130     public X7875_NewUnix() {
131         reset();
132     }
133 
134     @Override
135     public Object clone() throws CloneNotSupportedException {
136         return super.clone();
137     }
138 
139     @Override
140     public boolean equals(final Object o) {
141         if (o instanceof X7875_NewUnix) {
142             final X7875_NewUnix xf = (X7875_NewUnix) o;
143             // We assume uid and gid can never be null.
144             return version == xf.version && uid.equals(xf.uid) && gid.equals(xf.gid);
145         }
146         return false;
147     }
148 
149     /**
150      * The actual data to put into central directory data - without Header-ID or length specifier.
151      *
152      * @return get the data
153      */
154     @Override
155     public byte[] getCentralDirectoryData() {
156         return ByteUtils.EMPTY_BYTE_ARRAY;
157     }
158 
159     /**
160      * Length of the extra field in the central directory data - without Header-ID or length specifier.
161      *
162      * @return a {@code ZipShort} for the length of the data of this extra field
163      */
164     @Override
165     public ZipShort getCentralDirectoryLength() {
166         return ZERO;
167     }
168 
169     /**
170      * 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
171      * in case values above and including 2^31 are being used.
172      *
173      * @return the GID value.
174      */
175     public long getGID() {
176         return ZipUtil.toLong(gid);
177     }
178 
179     /**
180      * The Header-ID.
181      *
182      * @return the value for the header id for this extrafield
183      */
184     @Override
185     public ZipShort getHeaderId() {
186         return HEADER_ID;
187     }
188 
189     /**
190      * The actual data to put into local file data - without Header-ID or length specifier.
191      *
192      * @return get the data
193      */
194     @Override
195     public byte[] getLocalFileDataData() {
196         byte[] uidBytes = uid.toByteArray();
197         byte[] gidBytes = gid.toByteArray();
198         // BigInteger might prepend a leading-zero to force a positive representation
199         // (for example, so that the sign-bit is set to zero). We need to remove that
200         // before sending the number over the wire.
201         uidBytes = trimLeadingZeroesForceMinLength(uidBytes);
202         final int uidBytesLen = ArrayUtils.getLength(uidBytes);
203         gidBytes = trimLeadingZeroesForceMinLength(gidBytes);
204         final int gidBytesLen = ArrayUtils.getLength(gidBytes);
205         // Couldn't bring myself to just call getLocalFileDataLength() when we've
206         // already got the arrays right here. Yeah, yeah, I know, premature
207         // optimization is the root of all...
208         //
209         // The 3 comes from: version=1 + uidsize=1 + gidsize=1
210         final byte[] data = new byte[3 + uidBytesLen + gidBytesLen];
211         // reverse() switches byte array from big-endian to little-endian.
212         if (uidBytes != null) {
213             reverse(uidBytes);
214         }
215         if (gidBytes != null) {
216             reverse(gidBytes);
217         }
218         int pos = 0;
219         data[pos++] = unsignedIntToSignedByte(version);
220         data[pos++] = unsignedIntToSignedByte(uidBytesLen);
221         if (uidBytes != null) {
222             System.arraycopy(uidBytes, 0, data, pos, uidBytesLen);
223         }
224         pos += uidBytesLen;
225         data[pos++] = unsignedIntToSignedByte(gidBytesLen);
226         if (gidBytes != null) {
227             System.arraycopy(gidBytes, 0, data, pos, gidBytesLen);
228         }
229         return data;
230     }
231 
232     /**
233      * Length of the extra field in the local file data - without Header-ID or length specifier.
234      *
235      * @return a {@code ZipShort} for the length of the data of this extra field
236      */
237     @Override
238     public ZipShort getLocalFileDataLength() {
239         byte[] b = trimLeadingZeroesForceMinLength(uid.toByteArray());
240         final int uidSize = ArrayUtils.getLength(b);
241         b = trimLeadingZeroesForceMinLength(gid.toByteArray());
242         final int gidSize = ArrayUtils.getLength(b);
243         // The 3 comes from: version=1 + uidsize=1 + gidsize=1
244         return new ZipShort(3 + uidSize + gidSize);
245     }
246 
247     /**
248      * 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
249      * in case values above and including 2^31 are being used.
250      *
251      * @return the UID value.
252      */
253     public long getUID() {
254         return ZipUtil.toLong(uid);
255     }
256 
257     @Override
258     public int hashCode() {
259         int hc = -1234567 * version;
260         // Since most UIDs and GIDs are below 65,536, this is (hopefully!)
261         // a nice way to make sure typical UID and GID values impact the hash
262         // as much as possible.
263         hc ^= Integer.rotateLeft(uid.hashCode(), 16);
264         hc ^= gid.hashCode();
265         return hc;
266     }
267 
268     /**
269      * Doesn't do anything since this class doesn't store anything inside the central directory.
270      */
271     @Override
272     public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
273     }
274 
275     /**
276      * Populate data from this array as if it was in local file data.
277      *
278      * @param data   an array of bytes
279      * @param offset the start offset
280      * @param length the number of bytes in the array from offset
281      * @throws java.util.zip.ZipException on error
282      */
283     @Override
284     public void parseFromLocalFileData(final byte[] data, int offset, final int length) throws ZipException {
285         reset();
286         if (length < 3) {
287             throw new ZipException("X7875_NewUnix length is too short, only " + length + " bytes");
288         }
289         this.version = Byte.toUnsignedInt(data[offset++]);
290         final int uidSize = Byte.toUnsignedInt(data[offset++]);
291         if (uidSize + 3 > length) {
292             throw new ZipException("X7875_NewUnix invalid: uidSize " + uidSize + " doesn't fit into " + length + " bytes");
293         }
294         final byte[] uidBytes = Arrays.copyOfRange(data, offset, offset + uidSize);
295         offset += uidSize;
296         this.uid = new BigInteger(1, reverse(uidBytes)); // sign-bit forced positive
297 
298         final int gidSize = Byte.toUnsignedInt(data[offset++]);
299         if (uidSize + 3 + gidSize > length) {
300             throw new ZipException("X7875_NewUnix invalid: gidSize " + gidSize + " doesn't fit into " + length + " bytes");
301         }
302         final byte[] gidBytes = Arrays.copyOfRange(data, offset, offset + gidSize);
303         this.gid = new BigInteger(1, reverse(gidBytes)); // sign-bit forced positive
304     }
305 
306     /**
307      * Reset state back to newly constructed state. Helps us make sure parse() calls always generate clean results.
308      */
309     private void reset() {
310         // Typical UID/GID of the first non-root user created on a Unix system.
311         uid = ONE_THOUSAND;
312         gid = ONE_THOUSAND;
313     }
314 
315     /**
316      * Sets the GID.
317      *
318      * @param l GID value to set on this extra field.
319      */
320     public void setGID(final long l) {
321         this.gid = ZipUtil.longToBig(l);
322     }
323 
324     /**
325      * Sets the UID.
326      *
327      * @param l UID value to set on this extra field.
328      */
329     public void setUID(final long l) {
330         this.uid = ZipUtil.longToBig(l);
331     }
332 
333     /**
334      * Returns a String representation of this class useful for debugging purposes.
335      *
336      * @return A String representation of this class useful for debugging purposes.
337      */
338     @Override
339     public String toString() {
340         return "0x7875 Zip Extra Field: UID=" + uid + " GID=" + gid;
341     }
342 }