X7875_NewUnix.java

  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. import static org.apache.commons.compress.archivers.zip.ZipUtil.reverse;
  21. import static org.apache.commons.compress.archivers.zip.ZipUtil.signedByteToUnsignedInt;
  22. import static org.apache.commons.compress.archivers.zip.ZipUtil.unsignedIntToSignedByte;

  23. import java.io.Serializable;
  24. import java.math.BigInteger;
  25. import java.util.Arrays;
  26. import java.util.zip.ZipException;

  27. import org.apache.commons.compress.utils.ByteUtils;

  28. /**
  29.  * 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
  30.  * archive: zip-3.0.tar.gz/proginfo/extrafld.txt
  31.  *
  32.  * <pre>
  33.  * Local-header version:
  34.  *
  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)
  44.  *
  45.  * Central-header version:
  46.  *
  47.  * Value         Size        Description
  48.  * -----         ----        -----------
  49.  * 0x7855        Short       tag for this extra block type ("Ux")
  50.  * TSize         Short       total data size for this block (0)
  51.  * </pre>
  52.  *
  53.  * @since 1.5
  54.  */
  55. public class X7875_NewUnix implements ZipExtraField, Cloneable, Serializable {
  56.     static final ZipShort HEADER_ID = new ZipShort(0x7875);
  57.     private static final ZipShort ZERO = new ZipShort(0);
  58.     private static final BigInteger ONE_THOUSAND = BigInteger.valueOf(1000);
  59.     private static final long serialVersionUID = 1L;

  60.     /**
  61.      * 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
  62.      * length, and thus it really trims AND pads at the same time.
  63.      *
  64.      * @param array byte[] array to trim & pad.
  65.      * @return trimmed & padded byte[] array.
  66.      */
  67.     static byte[] trimLeadingZeroesForceMinLength(final byte[] array) {
  68.         if (array == null) {
  69.             return null;
  70.         }

  71.         int pos = 0;
  72.         for (final byte b : array) {
  73.             if (b != 0) {
  74.                 break;
  75.             }
  76.             pos++;
  77.         }

  78.         /*
  79.          *
  80.          * 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
  81.          * 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,
  82.          * 8 bits of zero) according to the spec.
  83.          *
  84.          * In the end I decided on MIN_LENGTH=1 for four reasons:
  85.          *
  86.          * 1.) We are adhering to the spec as far as I can tell, and so a consumer that cannot parse this is broken.
  87.          *
  88.          * 2.) Fundamentally, ZIP files are about shrinking things, so let's save a few bytes per entry while we can.
  89.          *
  90.          * 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
  91.          * probably thinking way too hard about this and no one cares!)
  92.          *
  93.          * 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
  94.          * ever restore UID/GID. unzip -X has no effect on my machine, even when run as root!!!!
  95.          *
  96.          * And thus it is decided: MIN_LENGTH=1.
  97.          *
  98.          * 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
  99.          * (requires changes to unit tests, though).
  100.          *
  101.          * And I am sorry that the time you spent reading this comment is now gone, and you can never have it back.
  102.          */
  103.         final int MIN_LENGTH = 1;

  104.         final byte[] trimmedArray = new byte[Math.max(MIN_LENGTH, array.length - pos)];
  105.         final int startPos = trimmedArray.length - (array.length - pos);
  106.         System.arraycopy(array, pos, trimmedArray, startPos, trimmedArray.length - startPos);
  107.         return trimmedArray;
  108.     }

  109.     private int version = 1; // always '1' according to current info-zip spec.
  110.     // BigInteger helps us with little-endian / big-endian conversions.
  111.     // (thanks to BigInteger.toByteArray() and a reverse() method we created).
  112.     // Also, the spec theoretically allows UID/GID up to 255 bytes long!
  113.     //
  114.     // NOTE: equals() and hashCode() currently assume these can never be null.
  115.     private BigInteger uid;

  116.     private BigInteger gid;

  117.     /**
  118.      * Constructor for X7875_NewUnix.
  119.      */
  120.     public X7875_NewUnix() {
  121.         reset();
  122.     }

  123.     @Override
  124.     public Object clone() throws CloneNotSupportedException {
  125.         return super.clone();
  126.     }

  127.     @Override
  128.     public boolean equals(final Object o) {
  129.         if (o instanceof X7875_NewUnix) {
  130.             final X7875_NewUnix xf = (X7875_NewUnix) o;
  131.             // We assume uid and gid can never be null.
  132.             return version == xf.version && uid.equals(xf.uid) && gid.equals(xf.gid);
  133.         }
  134.         return false;
  135.     }

  136.     /**
  137.      * The actual data to put into central directory data - without Header-ID or length specifier.
  138.      *
  139.      * @return get the data
  140.      */
  141.     @Override
  142.     public byte[] getCentralDirectoryData() {
  143.         return ByteUtils.EMPTY_BYTE_ARRAY;
  144.     }

  145.     /**
  146.      * Length of the extra field in the central directory data - without Header-ID or length specifier.
  147.      *
  148.      * @return a {@code ZipShort} for the length of the data of this extra field
  149.      */
  150.     @Override
  151.     public ZipShort getCentralDirectoryLength() {
  152.         return ZERO;
  153.     }

  154.     /**
  155.      * 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
  156.      * in case values above and including 2^31 are being used.
  157.      *
  158.      * @return the GID value.
  159.      */
  160.     public long getGID() {
  161.         return ZipUtil.bigToLong(gid);
  162.     }

  163.     /**
  164.      * The Header-ID.
  165.      *
  166.      * @return the value for the header id for this extrafield
  167.      */
  168.     @Override
  169.     public ZipShort getHeaderId() {
  170.         return HEADER_ID;
  171.     }

  172.     /**
  173.      * The actual data to put into local file data - without Header-ID or length specifier.
  174.      *
  175.      * @return get the data
  176.      */
  177.     @Override
  178.     public byte[] getLocalFileDataData() {
  179.         byte[] uidBytes = uid.toByteArray();
  180.         byte[] gidBytes = gid.toByteArray();

  181.         // BigInteger might prepend a leading-zero to force a positive representation
  182.         // (e.g., so that the sign-bit is set to zero). We need to remove that
  183.         // before sending the number over the wire.
  184.         uidBytes = trimLeadingZeroesForceMinLength(uidBytes);
  185.         final int uidBytesLen = uidBytes != null ? uidBytes.length : 0;
  186.         gidBytes = trimLeadingZeroesForceMinLength(gidBytes);
  187.         final int gidBytesLen = gidBytes != null ? gidBytes.length : 0;

  188.         // Couldn't bring myself to just call getLocalFileDataLength() when we've
  189.         // already got the arrays right here. Yeah, yeah, I know, premature
  190.         // optimization is the root of all...
  191.         //
  192.         // The 3 comes from: version=1 + uidsize=1 + gidsize=1
  193.         final byte[] data = new byte[3 + uidBytesLen + gidBytesLen];

  194.         // reverse() switches byte array from big-endian to little-endian.
  195.         if (uidBytes != null) {
  196.             reverse(uidBytes);
  197.         }
  198.         if (gidBytes != null) {
  199.             reverse(gidBytes);
  200.         }

  201.         int pos = 0;
  202.         data[pos++] = unsignedIntToSignedByte(version);
  203.         data[pos++] = unsignedIntToSignedByte(uidBytesLen);
  204.         if (uidBytes != null) {
  205.             System.arraycopy(uidBytes, 0, data, pos, uidBytesLen);
  206.         }
  207.         pos += uidBytesLen;
  208.         data[pos++] = unsignedIntToSignedByte(gidBytesLen);
  209.         if (gidBytes != null) {
  210.             System.arraycopy(gidBytes, 0, data, pos, gidBytesLen);
  211.         }
  212.         return data;
  213.     }

  214.     /**
  215.      * Length of the extra field in the local file data - without Header-ID or length specifier.
  216.      *
  217.      * @return a {@code ZipShort} for the length of the data of this extra field
  218.      */
  219.     @Override
  220.     public ZipShort getLocalFileDataLength() {
  221.         byte[] b = trimLeadingZeroesForceMinLength(uid.toByteArray());
  222.         final int uidSize = b == null ? 0 : b.length;
  223.         b = trimLeadingZeroesForceMinLength(gid.toByteArray());
  224.         final int gidSize = b == null ? 0 : b.length;

  225.         // The 3 comes from: version=1 + uidsize=1 + gidsize=1
  226.         return new ZipShort(3 + uidSize + gidSize);
  227.     }

  228.     /**
  229.      * 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
  230.      * in case values above and including 2^31 are being used.
  231.      *
  232.      * @return the UID value.
  233.      */
  234.     public long getUID() {
  235.         return ZipUtil.bigToLong(uid);
  236.     }

  237.     @Override
  238.     public int hashCode() {
  239.         int hc = -1234567 * version;
  240.         // Since most UIDs and GIDs are below 65,536, this is (hopefully!)
  241.         // a nice way to make sure typical UID and GID values impact the hash
  242.         // as much as possible.
  243.         hc ^= Integer.rotateLeft(uid.hashCode(), 16);
  244.         hc ^= gid.hashCode();
  245.         return hc;
  246.     }

  247.     /**
  248.      * Doesn't do anything since this class doesn't store anything inside the central directory.
  249.      */
  250.     @Override
  251.     public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
  252.     }

  253.     /**
  254.      * Populate data from this array as if it was in local file data.
  255.      *
  256.      * @param data   an array of bytes
  257.      * @param offset the start offset
  258.      * @param length the number of bytes in the array from offset
  259.      * @throws java.util.zip.ZipException on error
  260.      */
  261.     @Override
  262.     public void parseFromLocalFileData(final byte[] data, int offset, final int length) throws ZipException {
  263.         reset();
  264.         if (length < 3) {
  265.             throw new ZipException("X7875_NewUnix length is too short, only " + length + " bytes");
  266.         }
  267.         this.version = signedByteToUnsignedInt(data[offset++]);
  268.         final int uidSize = signedByteToUnsignedInt(data[offset++]);
  269.         if (uidSize + 3 > length) {
  270.             throw new ZipException("X7875_NewUnix invalid: uidSize " + uidSize + " doesn't fit into " + length + " bytes");
  271.         }
  272.         final byte[] uidBytes = Arrays.copyOfRange(data, offset, offset + uidSize);
  273.         offset += uidSize;
  274.         this.uid = new BigInteger(1, reverse(uidBytes)); // sign-bit forced positive

  275.         final int gidSize = signedByteToUnsignedInt(data[offset++]);
  276.         if (uidSize + 3 + gidSize > length) {
  277.             throw new ZipException("X7875_NewUnix invalid: gidSize " + gidSize + " doesn't fit into " + length + " bytes");
  278.         }
  279.         final byte[] gidBytes = Arrays.copyOfRange(data, offset, offset + gidSize);
  280.         this.gid = new BigInteger(1, reverse(gidBytes)); // sign-bit forced positive
  281.     }

  282.     /**
  283.      * Reset state back to newly constructed state. Helps us make sure parse() calls always generate clean results.
  284.      */
  285.     private void reset() {
  286.         // Typical UID/GID of the first non-root user created on a UNIX system.
  287.         uid = ONE_THOUSAND;
  288.         gid = ONE_THOUSAND;
  289.     }

  290.     /**
  291.      * Sets the GID.
  292.      *
  293.      * @param l GID value to set on this extra field.
  294.      */
  295.     public void setGID(final long l) {
  296.         this.gid = ZipUtil.longToBig(l);
  297.     }

  298.     /**
  299.      * Sets the UID.
  300.      *
  301.      * @param l UID value to set on this extra field.
  302.      */
  303.     public void setUID(final long l) {
  304.         this.uid = ZipUtil.longToBig(l);
  305.     }

  306.     /**
  307.      * Returns a String representation of this class useful for debugging purposes.
  308.      *
  309.      * @return A String representation of this class useful for debugging purposes.
  310.      */
  311.     @Override
  312.     public String toString() {
  313.         return "0x7875 Zip Extra Field: UID=" + uid + " GID=" + gid;
  314.     }
  315. }