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