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