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 * https://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 */ 019package org.apache.commons.compress.archivers.zip; 020 021import static org.apache.commons.compress.archivers.zip.ZipUtil.reverse; 022import static org.apache.commons.compress.archivers.zip.ZipUtil.unsignedIntToSignedByte; 023 024import java.io.Serializable; 025import java.math.BigInteger; 026import java.util.Arrays; 027import java.util.zip.ZipException; 028 029import org.apache.commons.compress.utils.ByteUtils; 030import org.apache.commons.lang3.ArrayUtils; 031 032/** 033 * 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 034 * archive: zip-3.0.tar.gz/proginfo/extrafld.txt 035 * 036 * <pre> 037 * Local-header version: 038 * 039 * Value Size Description 040 * ----- ---- ----------- 041 * 0x7875 Short tag for this extra block type ("ux") 042 * TSize Short total data size for this block 043 * Version 1 byte version of this extra field, currently 1 044 * UIDSize 1 byte Size of UID field 045 * UID Variable UID for this entry (little-endian) 046 * GIDSize 1 byte Size of GID field 047 * GID Variable GID for this entry (little-endian) 048 * 049 * Central-header version: 050 * 051 * Value Size Description 052 * ----- ---- ----------- 053 * 0x7855 Short tag for this extra block type ("Ux") 054 * TSize Short total data size for this block (0) 055 * </pre> 056 * 057 * @since 1.5 058 */ 059public class X7875_NewUnix implements ZipExtraField, Cloneable, Serializable { 060 static final ZipShort HEADER_ID = new ZipShort(0x7875); 061 private static final ZipShort ZERO = new ZipShort(0); 062 private static final BigInteger ONE_THOUSAND = BigInteger.valueOf(1000); 063 private static final long serialVersionUID = 1L; 064 065 /** 066 * 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 067 * length, and thus it really trims AND pads at the same time. 068 * 069 * @param array byte[] array to trim & pad. 070 * @return trimmed & padded byte[] array. 071 */ 072 static byte[] trimLeadingZeroesForceMinLength(final byte[] array) { 073 if (array == null) { 074 return null; 075 } 076 077 int pos = 0; 078 for (final byte b : array) { 079 if (b != 0) { 080 break; 081 } 082 pos++; 083 } 084 085 /* 086 * 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 087 * 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, 088 * 8 bits of zero) according to the spec. 089 * 090 * In the end I decided on MIN_LENGTH=1 for four reasons: 091 * 092 * 1.) We are adhering to the spec as far as I can tell, and so a consumer that cannot parse this is broken. 093 * 094 * 2.) Fundamentally, ZIP files are about shrinking things, so let's save a few bytes per entry while we can. 095 * 096 * 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, 097 * I am probably thinking way too hard about this and no one cares!) 098 * 099 * 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}