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