/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.commons.compress.archivers.zip;

import java.io.Serializable;
import java.math.BigInteger;
import java.util.zip.ZipException;

import static org.apache.commons.compress.archivers.zip.ZipUtil.reverse;
import static org.apache.commons.compress.archivers.zip.ZipUtil.signedByteToUnsignedInt;
import static org.apache.commons.compress.archivers.zip.ZipUtil.unsignedIntToSignedByte;

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 archive: zip-3.0.tar.gz/proginfo/extrafld.txt
Local-header version:
Value         Size        Description
-----         ----        -----------
0x7875        Short       tag for this extra block type ("ux")
TSize         Short       total data size for this block
Version       1 byte      version of this extra field, currently 1
UIDSize       1 byte      Size of UID field
UID           Variable    UID for this entry (little endian)
GIDSize       1 byte      Size of GID field
GID           Variable    GID for this entry (little endian)
Central-header version:
Value         Size        Description
-----         ----        -----------
0x7855        Short       tag for this extra block type ("Ux")
TSize         Short       total data size for this block (0)
Since:1.5
/** * An extra field that stores UNIX UID/GID data (owner &amp; group ownership) for a given * zip entry. We're using the field definition given in Info-Zip's source archive: * zip-3.0.tar.gz/proginfo/extrafld.txt * * <pre> * Local-header version: * * Value Size Description * ----- ---- ----------- * 0x7875 Short tag for this extra block type ("ux") * TSize Short total data size for this block * Version 1 byte version of this extra field, currently 1 * UIDSize 1 byte Size of UID field * UID Variable UID for this entry (little endian) * GIDSize 1 byte Size of GID field * GID Variable GID for this entry (little endian) * * Central-header version: * * Value Size Description * ----- ---- ----------- * 0x7855 Short tag for this extra block type ("Ux") * TSize Short total data size for this block (0) * </pre> * @since 1.5 */
public class X7875_NewUnix implements ZipExtraField, Cloneable, Serializable { private static final ZipShort HEADER_ID = new ZipShort(0x7875); private static final ZipShort ZERO = new ZipShort(0); private static final BigInteger ONE_THOUSAND = BigInteger.valueOf(1000); private static final long serialVersionUID = 1L; private int version = 1; // always '1' according to current info-zip spec. // BigInteger helps us with little-endian / big-endian conversions. // (thanks to BigInteger.toByteArray() and a reverse() method we created). // Also, the spec theoretically allows UID/GID up to 255 bytes long! // // NOTE: equals() and hashCode() currently assume these can never be null. private BigInteger uid; private BigInteger gid;
Constructor for X7875_NewUnix.
/** * Constructor for X7875_NewUnix. */
public X7875_NewUnix() { reset(); }
The Header-ID.
Returns:the value for the header id for this extrafield
/** * The Header-ID. * * @return the value for the header id for this extrafield */
@Override public ZipShort getHeaderId() { return HEADER_ID; }
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 in case values above and including 2^31 are being used.
Returns:the UID value.
/** * 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 in case values above * and including 2^31 are being used. * * @return the UID value. */
public long getUID() { return ZipUtil.bigToLong(uid); }
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 in case values above and including 2^31 are being used.
Returns:the GID value.
/** * 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 in case values above * and including 2^31 are being used. * * @return the GID value. */
public long getGID() { return ZipUtil.bigToLong(gid); }
Sets the UID.
Params:
  • l – UID value to set on this extra field.
/** * Sets the UID. * * @param l UID value to set on this extra field. */
public void setUID(final long l) { this.uid = ZipUtil.longToBig(l); }
Sets the GID.
Params:
  • l – GID value to set on this extra field.
/** * Sets the GID. * * @param l GID value to set on this extra field. */
public void setGID(final long l) { this.gid = ZipUtil.longToBig(l); }
Length of the extra field in the local file data - without Header-ID or length specifier.
Returns:a ZipShort for the length of the data of this extra field
/** * Length of the extra field in the local file data - without * Header-ID or length specifier. * * @return a <code>ZipShort</code> for the length of the data of this extra field */
@Override public ZipShort getLocalFileDataLength() { byte[] b = trimLeadingZeroesForceMinLength(uid.toByteArray()); final int uidSize = b == null ? 0 : b.length; b = trimLeadingZeroesForceMinLength(gid.toByteArray()); final int gidSize = b == null ? 0 : b.length; // The 3 comes from: version=1 + uidsize=1 + gidsize=1 return new ZipShort(3 + uidSize + gidSize); }
Length of the extra field in the central directory data - without Header-ID or length specifier.
Returns:a ZipShort for the length of the data of this extra field
/** * Length of the extra field in the central directory data - without * Header-ID or length specifier. * * @return a <code>ZipShort</code> for the length of the data of this extra field */
@Override public ZipShort getCentralDirectoryLength() { return ZERO; }
The actual data to put into local file data - without Header-ID or length specifier.
Returns:get the data
/** * The actual data to put into local file data - without Header-ID * or length specifier. * * @return get the data */
@Override public byte[] getLocalFileDataData() { byte[] uidBytes = uid.toByteArray(); byte[] gidBytes = gid.toByteArray(); // BigInteger might prepend a leading-zero to force a positive representation // (e.g., so that the sign-bit is set to zero). We need to remove that // before sending the number over the wire. uidBytes = trimLeadingZeroesForceMinLength(uidBytes); int uidBytesLen = uidBytes != null ? uidBytes.length : 0; gidBytes = trimLeadingZeroesForceMinLength(gidBytes); int gidBytesLen = gidBytes != null ? gidBytes.length : 0; // Couldn't bring myself to just call getLocalFileDataLength() when we've // already got the arrays right here. Yeah, yeah, I know, premature // optimization is the root of all... // // The 3 comes from: version=1 + uidsize=1 + gidsize=1 final byte[] data = new byte[3 + uidBytesLen + gidBytesLen]; // reverse() switches byte array from big-endian to little-endian. if (uidBytes != null) { reverse(uidBytes); } if (gidBytes != null) { reverse(gidBytes); } int pos = 0; data[pos++] = unsignedIntToSignedByte(version); data[pos++] = unsignedIntToSignedByte(uidBytesLen); if (uidBytes != null) { System.arraycopy(uidBytes, 0, data, pos, uidBytesLen); } pos += uidBytesLen; data[pos++] = unsignedIntToSignedByte(gidBytesLen); if (gidBytes != null) { System.arraycopy(gidBytes, 0, data, pos, gidBytesLen); } return data; }
The actual data to put into central directory data - without Header-ID or length specifier.
Returns:get the data
/** * The actual data to put into central directory data - without Header-ID * or length specifier. * * @return get the data */
@Override public byte[] getCentralDirectoryData() { return new byte[0]; }
Populate data from this array as if it was in local file data.
Params:
  • data – an array of bytes
  • offset – the start offset
  • length – the number of bytes in the array from offset
Throws:
/** * Populate data from this array as if it was in local file data. * * @param data an array of bytes * @param offset the start offset * @param length the number of bytes in the array from offset * @throws java.util.zip.ZipException on error */
@Override public void parseFromLocalFileData( final byte[] data, int offset, final int length ) throws ZipException { reset(); this.version = signedByteToUnsignedInt(data[offset++]); final int uidSize = signedByteToUnsignedInt(data[offset++]); final byte[] uidBytes = new byte[uidSize]; System.arraycopy(data, offset, uidBytes, 0, uidSize); offset += uidSize; this.uid = new BigInteger(1, reverse(uidBytes)); // sign-bit forced positive final int gidSize = signedByteToUnsignedInt(data[offset++]); final byte[] gidBytes = new byte[gidSize]; System.arraycopy(data, offset, gidBytes, 0, gidSize); this.gid = new BigInteger(1, reverse(gidBytes)); // sign-bit forced positive }
Doesn't do anything since this class doesn't store anything inside the central directory.
/** * Doesn't do anything since this class doesn't store anything * inside the central directory. */
@Override public void parseFromCentralDirectoryData( final byte[] buffer, final int offset, final int length ) throws ZipException { }
Reset state back to newly constructed state. Helps us make sure parse() calls always generate clean results.
/** * Reset state back to newly constructed state. Helps us make sure * parse() calls always generate clean results. */
private void reset() { // Typical UID/GID of the first non-root user created on a unix system. uid = ONE_THOUSAND; gid = ONE_THOUSAND; }
Returns a String representation of this class useful for debugging purposes.
Returns:A String representation of this class useful for debugging purposes.
/** * Returns a String representation of this class useful for * debugging purposes. * * @return A String representation of this class useful for * debugging purposes. */
@Override public String toString() { return "0x7875 Zip Extra Field: UID=" + uid + " GID=" + gid; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public boolean equals(final Object o) { if (o instanceof X7875_NewUnix) { final X7875_NewUnix xf = (X7875_NewUnix) o; // We assume uid and gid can never be null. return version == xf.version && uid.equals(xf.uid) && gid.equals(xf.gid); } return false; } @Override public int hashCode() { int hc = -1234567 * version; // Since most UID's and GID's are below 65,536, this is (hopefully!) // a nice way to make sure typical UID and GID values impact the hash // as much as possible. hc ^= Integer.rotateLeft(uid.hashCode(), 16); hc ^= gid.hashCode(); return hc; }
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 length, and thus it really trims AND pads at the same time.
Params:
  • array – byte[] array to trim & pad.
Returns:trimmed & padded byte[] array.
/** * 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 length, and thus it really trims AND pads * at the same time. * * @param array byte[] array to trim & pad. * @return trimmed & padded byte[] array. */
static byte[] trimLeadingZeroesForceMinLength(final byte[] array) { if (array == null) { return array; } int pos = 0; for (final byte b : array) { if (b == 0) { pos++; } else { break; } } /* 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 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, 8 bits of zero) according to the spec. In the end I decided on MIN_LENGTH=1 for four reasons: 1.) We are adhering to the spec as far as I can tell, and so a consumer that cannot parse this is broken. 2.) Fundamentally, zip files are about shrinking things, so let's save a few bytes per entry while we can. 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 probably thinking way too hard about this and no one cares!) 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 ever restore UID/GID. unzip -X has no effect on my machine, even when run as root!!!! And thus it is decided: MIN_LENGTH=1. 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 (requires changes to unit tests, though). And I am sorry that the time you spent reading this comment is now gone and you can never have it back. */ final int MIN_LENGTH = 1; final byte[] trimmedArray = new byte[Math.max(MIN_LENGTH, array.length - pos)]; final int startPos = trimmedArray.length - (array.length - pos); System.arraycopy(array, pos, trimmedArray, startPos, trimmedArray.length - startPos); return trimmedArray; } }