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