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