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