001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     * http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.commons.compress.archivers.zip;
020    
021    import java.io.Serializable;
022    import java.math.BigInteger;
023    import java.util.zip.ZipException;
024    
025    import static org.apache.commons.compress.archivers.zip.ZipUtil.reverse;
026    import static org.apache.commons.compress.archivers.zip.ZipUtil.signedByteToUnsignedInt;
027    import static org.apache.commons.compress.archivers.zip.ZipUtil.unsignedIntToSignedByte;
028    
029    /**
030     * An extra field that stores UNIX UID/GID data (owner & group ownership) for a given
031     * zip entry.  We're using the field definition given in Info-Zip's source archive:
032     * zip-3.0.tar.gz/proginfo/extrafld.txt
033     * <p/>
034     * <pre>
035     * Value         Size        Description
036     * -----         ----        -----------
037     * 0x7875        Short       tag for this extra block type ("ux")
038     * TSize         Short       total data size for this block
039     * Version       1 byte      version of this extra field, currently 1
040     * UIDSize       1 byte      Size of UID field
041     * UID           Variable    UID for this entry (little endian)
042     * GIDSize       1 byte      Size of GID field
043     * GID           Variable    GID for this entry (little endian)
044     * </pre>
045     * @since 1.5
046     */
047    public class X7875_NewUnix implements ZipExtraField, Cloneable, Serializable {
048        private static final ZipShort HEADER_ID = new ZipShort(0x7875);
049        private static final BigInteger ONE_THOUSAND = BigInteger.valueOf(1000);
050        private static final long serialVersionUID = 1L;
051    
052        private int version = 1; // always '1' according to current info-zip spec.
053    
054        // BigInteger helps us with little-endian / big-endian conversions.
055        // (thanks to BigInteger.toByteArray() and a reverse() method we created).
056        // Also, the spec theoretically allows UID/GID up to 255 bytes long!
057        //
058        // NOTE:  equals() and hashCode() currently assume these can never be null.
059        private BigInteger uid;
060        private BigInteger gid;
061    
062        /**
063         * Constructor for X7875_NewUnix.
064         */
065        public X7875_NewUnix() {
066            reset();
067        }
068    
069        /**
070         * The Header-ID.
071         *
072         * @return the value for the header id for this extrafield
073         */
074        public ZipShort getHeaderId() {
075            return HEADER_ID;
076        }
077    
078        /**
079         * Gets the UID as a long.  UID is typically a 32 bit unsigned
080         * value on most UNIX systems, so we return a long to avoid
081         * integer overflow into the negatives in case values above
082         * and including 2^31 are being used.
083         *
084         * @return the UID value.
085         */
086        public long getUID() { return ZipUtil.bigToLong(uid); }
087    
088        /**
089         * Gets the GID as a long.  GID is typically a 32 bit unsigned
090         * value on most UNIX systems, so we return a long to avoid
091         * integer overflow into the negatives in case values above
092         * and including 2^31 are being used.
093         *
094         * @return the GID value.
095         */
096        public long getGID() { return ZipUtil.bigToLong(gid); }
097    
098        /**
099         * Sets the UID.
100         *
101         * @param l UID value to set on this extra field.
102         */
103        public void setUID(long l) {
104            this.uid = ZipUtil.longToBig(l);
105        }
106    
107        /**
108         * Sets the GID.
109         *
110         * @param l GID value to set on this extra field.
111         */
112        public void setGID(long l) {
113            this.gid = ZipUtil.longToBig(l);
114        }
115    
116        /**
117         * Length of the extra field in the local file data - without
118         * Header-ID or length specifier.
119         *
120         * @return a <code>ZipShort</code> for the length of the data of this extra field
121         */
122        public ZipShort getLocalFileDataLength() {
123            int uidSize = trimLeadingZeroesForceMinLength(uid.toByteArray()).length;
124            int gidSize = trimLeadingZeroesForceMinLength(gid.toByteArray()).length;
125    
126            // The 3 comes from:  version=1 + uidsize=1 + gidsize=1
127            return new ZipShort(3 + uidSize + gidSize);
128        }
129    
130        /**
131         * Length of the extra field in the central directory data - without
132         * Header-ID or length specifier.
133         *
134         * @return a <code>ZipShort</code> for the length of the data of this extra field
135         */
136        public ZipShort getCentralDirectoryLength() {
137            return getLocalFileDataLength();  // No different than local version.
138        }
139    
140        /**
141         * The actual data to put into local file data - without Header-ID
142         * or length specifier.
143         *
144         * @return get the data
145         */
146        public byte[] getLocalFileDataData() {
147            byte[] uidBytes = uid.toByteArray();
148            byte[] gidBytes = gid.toByteArray();
149    
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);
155    
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...
159            //
160            // The 3 comes from:  version=1 + uidsize=1 + gidsize=1
161            byte[] data = new byte[3 + uidBytes.length + gidBytes.length];
162    
163            // reverse() switches byte array from big-endian to little-endian.
164            reverse(uidBytes);
165            reverse(gidBytes);
166    
167            int pos = 0;
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);
174            return data;
175        }
176    
177        /**
178         * The actual data to put into central directory data - without Header-ID
179         * or length specifier.
180         *
181         * @return get the data
182         */
183        public byte[] getCentralDirectoryData() {
184            return getLocalFileDataData();
185        }
186    
187        /**
188         * Populate data from this array as if it was in local file data.
189         *
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
194         */
195        public void parseFromLocalFileData(
196                byte[] data, int offset, int length
197        ) throws ZipException {
198            reset();
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);
203            offset += uidSize;
204            this.uid = new BigInteger(1, reverse(uidBytes)); // sign-bit forced positive
205    
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
210        }
211    
212        /**
213         * Doesn't do anything special since this class always uses the
214         * same data in central directory and local file data.
215         */
216        public void parseFromCentralDirectoryData(
217                byte[] buffer, int offset, int length
218        ) throws ZipException {
219            reset();
220            parseFromLocalFileData(buffer, offset, length);
221        }
222    
223        /**
224         * Reset state back to newly constructed state.  Helps us make sure
225         * parse() calls always generate clean results.
226         */
227        private void reset() {
228            // Typical UID/GID of the first non-root user created on a unix system.
229            uid = ONE_THOUSAND;
230            gid = ONE_THOUSAND;
231        }
232    
233        /**
234         * Returns a String representation of this class useful for
235         * debugging purposes.
236         *
237         * @return A String representation of this class useful for
238         *         debugging purposes.
239         */
240        @Override
241        public String toString() {
242            return "0x7875 Zip Extra Field: UID=" + uid + " GID=" + gid;
243        }
244    
245        @Override
246        public Object clone() throws CloneNotSupportedException {
247            return super.clone();
248        }
249    
250        @Override
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);
256            }
257            return false;
258        }
259    
260        @Override
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();
268            return hc;
269        }
270    
271        /**
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
275         * at the same time.
276         *
277         * @param array byte[] array to trim & pad.
278         * @return trimmed & padded byte[] array.
279         */
280        static byte[] trimLeadingZeroesForceMinLength(byte[] array) {
281            if (array == null) {
282                return array;
283            }
284    
285            int pos = 0;
286            for (byte b : array) {
287                if (b == 0) {
288                    pos++;
289                } else {
290                    break;
291                }
292            }
293    
294            /*
295    
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
301            the spec.
302    
303            In the end I decided on MIN_LENGTH=1 for four reasons:
304    
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.
307    
308            2.)  Fundamentally, zip files are about shrinking things, so
309                 let's save a few bytes per entry while we can.
310    
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!)
315    
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!!!!
320    
321            And thus it is decided:  MIN_LENGTH=1.
322    
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).
326    
327            And I am sorry that the time you spent reading this comment is now
328            gone and you can never have it back.
329    
330            */
331            final int MIN_LENGTH = 1;
332    
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);
336            return trimmedArray;
337        }
338    }