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 * http://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 java.io.Serializable; 022import java.math.BigInteger; 023import java.util.Arrays; 024import java.util.zip.ZipException; 025 026import org.apache.commons.compress.utils.ByteUtils; 027 028import static org.apache.commons.compress.archivers.zip.ZipUtil.reverse; 029import static org.apache.commons.compress.archivers.zip.ZipUtil.signedByteToUnsignedInt; 030import static org.apache.commons.compress.archivers.zip.ZipUtil.unsignedIntToSignedByte; 031 032/** 033 * An extra field that stores UNIX UID/GID data (owner & group ownership) for a given 034 * zip entry. We're using the field definition given in Info-Zip's source archive: 035 * zip-3.0.tar.gz/proginfo/extrafld.txt 036 * 037 * <pre> 038 * Local-header version: 039 * 040 * Value Size Description 041 * ----- ---- ----------- 042 * 0x7875 Short tag for this extra block type ("ux") 043 * TSize Short total data size for this block 044 * Version 1 byte version of this extra field, currently 1 045 * UIDSize 1 byte Size of UID field 046 * UID Variable UID for this entry (little endian) 047 * GIDSize 1 byte Size of GID field 048 * GID Variable GID for this entry (little endian) 049 * 050 * Central-header version: 051 * 052 * Value Size Description 053 * ----- ---- ----------- 054 * 0x7855 Short tag for this extra block type ("Ux") 055 * TSize Short total data size for this block (0) 056 * </pre> 057 * @since 1.5 058 */ 059public class X7875_NewUnix implements ZipExtraField, Cloneable, Serializable { 060 private 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 private int version = 1; // always '1' according to current info-zip spec. 066 067 // BigInteger helps us with little-endian / big-endian conversions. 068 // (thanks to BigInteger.toByteArray() and a reverse() method we created). 069 // Also, the spec theoretically allows UID/GID up to 255 bytes long! 070 // 071 // NOTE: equals() and hashCode() currently assume these can never be null. 072 private BigInteger uid; 073 private BigInteger gid; 074 075 /** 076 * Constructor for X7875_NewUnix. 077 */ 078 public X7875_NewUnix() { 079 reset(); 080 } 081 082 /** 083 * The Header-ID. 084 * 085 * @return the value for the header id for this extrafield 086 */ 087 @Override 088 public ZipShort getHeaderId() { 089 return HEADER_ID; 090 } 091 092 /** 093 * Gets the UID as a long. UID is typically a 32 bit unsigned 094 * value on most UNIX systems, so we return a long to avoid 095 * integer overflow into the negatives in case values above 096 * and including 2^31 are being used. 097 * 098 * @return the UID value. 099 */ 100 public long getUID() { return ZipUtil.bigToLong(uid); } 101 102 /** 103 * Gets the GID as a long. GID is typically a 32 bit unsigned 104 * value on most UNIX systems, so we return a long to avoid 105 * integer overflow into the negatives in case values above 106 * and including 2^31 are being used. 107 * 108 * @return the GID value. 109 */ 110 public long getGID() { return ZipUtil.bigToLong(gid); } 111 112 /** 113 * Sets the UID. 114 * 115 * @param l UID value to set on this extra field. 116 */ 117 public void setUID(final long l) { 118 this.uid = ZipUtil.longToBig(l); 119 } 120 121 /** 122 * Sets the GID. 123 * 124 * @param l GID value to set on this extra field. 125 */ 126 public void setGID(final long l) { 127 this.gid = ZipUtil.longToBig(l); 128 } 129 130 /** 131 * Length of the extra field in the local file data - without 132 * Header-ID or length specifier. 133 * 134 * @return a <code>ZipShort</code> for the length of the data of this extra field 135 */ 136 @Override 137 public ZipShort getLocalFileDataLength() { 138 byte[] b = trimLeadingZeroesForceMinLength(uid.toByteArray()); 139 final int uidSize = b == null ? 0 : b.length; 140 b = trimLeadingZeroesForceMinLength(gid.toByteArray()); 141 final int gidSize = b == null ? 0 : b.length; 142 143 // The 3 comes from: version=1 + uidsize=1 + gidsize=1 144 return new ZipShort(3 + uidSize + gidSize); 145 } 146 147 /** 148 * Length of the extra field in the central directory data - without 149 * Header-ID or length specifier. 150 * 151 * @return a <code>ZipShort</code> for the length of the data of this extra field 152 */ 153 @Override 154 public ZipShort getCentralDirectoryLength() { 155 return ZERO; 156 } 157 158 /** 159 * The actual data to put into local file data - without Header-ID 160 * or length specifier. 161 * 162 * @return get the data 163 */ 164 @Override 165 public byte[] getLocalFileDataData() { 166 byte[] uidBytes = uid.toByteArray(); 167 byte[] gidBytes = gid.toByteArray(); 168 169 // BigInteger might prepend a leading-zero to force a positive representation 170 // (e.g., so that the sign-bit is set to zero). We need to remove that 171 // before sending the number over the wire. 172 uidBytes = trimLeadingZeroesForceMinLength(uidBytes); 173 final int uidBytesLen = uidBytes != null ? uidBytes.length : 0; 174 gidBytes = trimLeadingZeroesForceMinLength(gidBytes); 175 final int gidBytesLen = gidBytes != null ? gidBytes.length : 0; 176 177 // Couldn't bring myself to just call getLocalFileDataLength() when we've 178 // already got the arrays right here. Yeah, yeah, I know, premature 179 // optimization is the root of all... 180 // 181 // The 3 comes from: version=1 + uidsize=1 + gidsize=1 182 final byte[] data = new byte[3 + uidBytesLen + gidBytesLen]; 183 184 // reverse() switches byte array from big-endian to little-endian. 185 if (uidBytes != null) { 186 reverse(uidBytes); 187 } 188 if (gidBytes != null) { 189 reverse(gidBytes); 190 } 191 192 int pos = 0; 193 data[pos++] = unsignedIntToSignedByte(version); 194 data[pos++] = unsignedIntToSignedByte(uidBytesLen); 195 if (uidBytes != null) { 196 System.arraycopy(uidBytes, 0, data, pos, uidBytesLen); 197 } 198 pos += uidBytesLen; 199 data[pos++] = unsignedIntToSignedByte(gidBytesLen); 200 if (gidBytes != null) { 201 System.arraycopy(gidBytes, 0, data, pos, gidBytesLen); 202 } 203 return data; 204 } 205 206 /** 207 * The actual data to put into central directory data - without Header-ID 208 * or length specifier. 209 * 210 * @return get the data 211 */ 212 @Override 213 public byte[] getCentralDirectoryData() { 214 return ByteUtils.EMPTY_BYTE_ARRAY; 215 } 216 217 /** 218 * Populate data from this array as if it was in local file data. 219 * 220 * @param data an array of bytes 221 * @param offset the start offset 222 * @param length the number of bytes in the array from offset 223 * @throws java.util.zip.ZipException on error 224 */ 225 @Override 226 public void parseFromLocalFileData( 227 final byte[] data, int offset, final int length 228 ) throws ZipException { 229 reset(); 230 if (length < 3) { 231 throw new ZipException("X7875_NewUnix length is too short, only " 232 + length + " bytes"); 233 } 234 this.version = signedByteToUnsignedInt(data[offset++]); 235 final int uidSize = signedByteToUnsignedInt(data[offset++]); 236 if (uidSize + 3 > length) { 237 throw new ZipException("X7875_NewUnix invalid: uidSize " + uidSize 238 + " doesn't fit into " + length + " bytes"); 239 } 240 final byte[] uidBytes = Arrays.copyOfRange(data, offset, offset + uidSize); 241 offset += uidSize; 242 this.uid = new BigInteger(1, reverse(uidBytes)); // sign-bit forced positive 243 244 final int gidSize = signedByteToUnsignedInt(data[offset++]); 245 if (uidSize + 3 + gidSize > length) { 246 throw new ZipException("X7875_NewUnix invalid: gidSize " + gidSize 247 + " doesn't fit into " + length + " bytes"); 248 } 249 final byte[] gidBytes = Arrays.copyOfRange(data, offset, offset + gidSize); 250 this.gid = new BigInteger(1, reverse(gidBytes)); // sign-bit forced positive 251 } 252 253 /** 254 * Doesn't do anything since this class doesn't store anything 255 * inside the central directory. 256 */ 257 @Override 258 public void parseFromCentralDirectoryData( 259 final byte[] buffer, final int offset, final int length 260 ) throws ZipException { 261 } 262 263 /** 264 * Reset state back to newly constructed state. Helps us make sure 265 * parse() calls always generate clean results. 266 */ 267 private void reset() { 268 // Typical UID/GID of the first non-root user created on a unix system. 269 uid = ONE_THOUSAND; 270 gid = ONE_THOUSAND; 271 } 272 273 /** 274 * Returns a String representation of this class useful for 275 * debugging purposes. 276 * 277 * @return A String representation of this class useful for 278 * debugging purposes. 279 */ 280 @Override 281 public String toString() { 282 return "0x7875 Zip Extra Field: UID=" + uid + " GID=" + gid; 283 } 284 285 @Override 286 public Object clone() throws CloneNotSupportedException { 287 return super.clone(); 288 } 289 290 @Override 291 public boolean equals(final Object o) { 292 if (o instanceof X7875_NewUnix) { 293 final X7875_NewUnix xf = (X7875_NewUnix) o; 294 // We assume uid and gid can never be null. 295 return version == xf.version && uid.equals(xf.uid) && gid.equals(xf.gid); 296 } 297 return false; 298 } 299 300 @Override 301 public int hashCode() { 302 int hc = -1234567 * version; 303 // Since most UID's and GID's are below 65,536, this is (hopefully!) 304 // a nice way to make sure typical UID and GID values impact the hash 305 // as much as possible. 306 hc ^= Integer.rotateLeft(uid.hashCode(), 16); 307 hc ^= gid.hashCode(); 308 return hc; 309 } 310 311 /** 312 * Not really for external usage, but marked "package" visibility 313 * to help us JUnit it. Trims a byte array of leading zeroes while 314 * also enforcing a minimum length, and thus it really trims AND pads 315 * at the same time. 316 * 317 * @param array byte[] array to trim & pad. 318 * @return trimmed & padded byte[] array. 319 */ 320 static byte[] trimLeadingZeroesForceMinLength(final byte[] array) { 321 if (array == null) { 322 return array; 323 } 324 325 int pos = 0; 326 for (final byte b : array) { 327 if (b != 0) { 328 break; 329 } 330 pos++; 331 } 332 333 /* 334 335 I agonized over my choice of MIN_LENGTH=1. Here's the situation: 336 InfoZip (the tool I am using to test interop) always sets these 337 to length=4. And so a UID of 0 (typically root) for example is 338 encoded as {4,0,0,0,0} (len=4, 32 bits of zero), when it could just 339 as easily be encoded as {1,0} (len=1, 8 bits of zero) according to 340 the spec. 341 342 In the end I decided on MIN_LENGTH=1 for four reasons: 343 344 1.) We are adhering to the spec as far as I can tell, and so 345 a consumer that cannot parse this is broken. 346 347 2.) Fundamentally, zip files are about shrinking things, so 348 let's save a few bytes per entry while we can. 349 350 3.) Of all the people creating zip files using commons- 351 compress, how many care about UNIX UID/GID attributes 352 of the files they store? (e.g., I am probably thinking 353 way too hard about this and no one cares!) 354 355 4.) InfoZip's tool, even though it carefully stores every UID/GID 356 for every file zipped on a unix machine (by default) currently 357 appears unable to ever restore UID/GID. 358 unzip -X has no effect on my machine, even when run as root!!!! 359 360 And thus it is decided: MIN_LENGTH=1. 361 362 If anyone runs into interop problems from this, feel free to set 363 it to MIN_LENGTH=4 at some future time, and then we will behave 364 exactly like InfoZip (requires changes to unit tests, though). 365 366 And I am sorry that the time you spent reading this comment is now 367 gone and you can never have it back. 368 369 */ 370 final int MIN_LENGTH = 1; 371 372 final byte[] trimmedArray = new byte[Math.max(MIN_LENGTH, array.length - pos)]; 373 final int startPos = trimmedArray.length - (array.length - pos); 374 System.arraycopy(array, pos, trimmedArray, startPos, trimmedArray.length - startPos); 375 return trimmedArray; 376 } 377}