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 static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT; 022import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD; 023 024import java.nio.charset.Charset; 025import java.util.zip.CRC32; 026import java.util.zip.ZipException; 027 028/** 029 * Adds UNIX file permission and UID/GID fields as well as symbolic link handling. 030 * 031 * <p> 032 * This class uses the ASi extra field in the format: 033 * </p> 034 * 035 * <pre> 036 * Value Size Description 037 * ----- ---- ----------- 038 * (Unix3) 0x756e Short tag for this extra block type 039 * TSize Short total data size for this block 040 * CRC Long CRC-32 of the remaining data 041 * Mode Short file permissions 042 * SizDev Long symlink'd size OR major/minor dev num 043 * UID Short user ID 044 * GID Short group ID 045 * (var.) variable symbolic link file name 046 * </pre> 047 * <p> 048 * taken from appnote.iz (Info-ZIP note, 981119) found at <a href="ftp://ftp.uu.net/pub/archiving/zip/doc/">ftp://ftp.uu.net/pub/archiving/zip/doc/</a> 049 * </p> 050 * 051 * <p> 052 * Short is two bytes and Long is four bytes in big-endian byte and word order, device numbers are currently not supported. 053 * </p> 054 * 055 * @NotThreadSafe 056 * 057 * <p> 058 * Since the documentation this class is based upon doesn't mention the character encoding of the file name at all, it is assumed that it uses 059 * the current platform's default encoding. 060 * </p> 061 */ 062public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { 063 064 static final ZipShort HEADER_ID = new ZipShort(0x756E); 065 private static final int MIN_SIZE = WORD + SHORT + WORD + SHORT + SHORT; 066 067 /** 068 * Standard UNIX stat(2) file mode. 069 */ 070 private int mode; 071 /** 072 * User ID. 073 */ 074 private int uid; 075 /** 076 * Group ID. 077 */ 078 private int gid; 079 /** 080 * File this entry points to, if it is a symbolic link. 081 * 082 * <p> 083 * empty string - if entry is not a symbolic link. 084 * </p> 085 */ 086 private String link = ""; 087 /** 088 * Is this an entry for a directory? 089 */ 090 private boolean dirFlag; 091 092 /** 093 * Instance used to calculate checksums. 094 */ 095 private CRC32 crc = new CRC32(); 096 097 /** Constructor for AsiExtraField. */ 098 public AsiExtraField() { 099 } 100 101 @Override 102 public Object clone() { 103 try { 104 final AsiExtraField cloned = (AsiExtraField) super.clone(); 105 cloned.crc = new CRC32(); 106 return cloned; 107 } catch (final CloneNotSupportedException cnfe) { 108 // impossible 109 throw new UnsupportedOperationException(cnfe); // NOSONAR 110 } 111 } 112 113 /** 114 * Delegate to local file data. 115 * 116 * @return the local file data 117 */ 118 @Override 119 public byte[] getCentralDirectoryData() { 120 return getLocalFileDataData(); 121 } 122 123 /** 124 * Delegate to local file data. 125 * 126 * @return the centralDirectory length 127 */ 128 @Override 129 public ZipShort getCentralDirectoryLength() { 130 return getLocalFileDataLength(); 131 } 132 133 /** 134 * Gets the group id. 135 * 136 * @return the group id 137 */ 138 public int getGroupId() { 139 return gid; 140 } 141 142 /** 143 * The Header-ID. 144 * 145 * @return the value for the header id for this extrafield 146 */ 147 @Override 148 public ZipShort getHeaderId() { 149 return HEADER_ID; 150 } 151 152 /** 153 * Name of linked file 154 * 155 * @return name of the file this entry links to if it is a symbolic link, the empty string otherwise. 156 */ 157 public String getLinkedFile() { 158 return link; 159 } 160 161 /** 162 * The actual data to put into local file data - without Header-ID or length specifier. 163 * 164 * @return get the data 165 */ 166 @Override 167 public byte[] getLocalFileDataData() { 168 // CRC will be added later 169 final byte[] data = new byte[getLocalFileDataLength().getValue() - WORD]; 170 System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2); 171 172 final byte[] linkArray = getLinkedFile().getBytes(Charset.defaultCharset()); // Uses default charset - see class Javadoc 173 // CheckStyle:MagicNumber OFF 174 System.arraycopy(ZipLong.getBytes(linkArray.length), 0, data, 2, WORD); 175 176 System.arraycopy(ZipShort.getBytes(getUserId()), 0, data, 6, 2); 177 System.arraycopy(ZipShort.getBytes(getGroupId()), 0, data, 8, 2); 178 179 System.arraycopy(linkArray, 0, data, 10, linkArray.length); 180 // CheckStyle:MagicNumber ON 181 182 crc.reset(); 183 crc.update(data); 184 final long checksum = crc.getValue(); 185 186 final byte[] result = new byte[data.length + WORD]; 187 System.arraycopy(ZipLong.getBytes(checksum), 0, result, 0, WORD); 188 System.arraycopy(data, 0, result, WORD, data.length); 189 return result; 190 } 191 192 /** 193 * Length of the extra field in the local file data - without Header-ID or length specifier. 194 * 195 * @return a {@code ZipShort} for the length of the data of this extra field 196 */ 197 @Override 198 public ZipShort getLocalFileDataLength() { 199 // @formatter:off 200 return new ZipShort(WORD // CRC 201 + 2 // Mode 202 + WORD // SizDev 203 + 2 // UID 204 + 2 // GID 205 + getLinkedFile().getBytes(Charset.defaultCharset()).length); 206 // Uses default charset - see class Javadoc 207 // @formatter:on 208 } 209 210 /** 211 * File mode of this file. 212 * 213 * @return the file mode 214 */ 215 public int getMode() { 216 return mode; 217 } 218 219 /** 220 * Gets the file mode for given permissions with the correct file type. 221 * 222 * @param mode the mode 223 * @return the type with the mode 224 */ 225 protected int getMode(final int mode) { 226 int type = FILE_FLAG; 227 if (isLink()) { 228 type = LINK_FLAG; 229 } else if (isDirectory()) { 230 type = DIR_FLAG; 231 } 232 return type | mode & PERM_MASK; 233 } 234 235 /** 236 * Gets the user id. 237 * 238 * @return the user id 239 */ 240 public int getUserId() { 241 return uid; 242 } 243 244 /** 245 * Is this entry a directory? 246 * 247 * @return true if this entry is a directory 248 */ 249 public boolean isDirectory() { 250 return dirFlag && !isLink(); 251 } 252 253 /** 254 * Is this entry a symbolic link? 255 * 256 * @return true if this is a symbolic link 257 */ 258 public boolean isLink() { 259 return !getLinkedFile().isEmpty(); 260 } 261 262 /** 263 * Doesn't do anything special since this class always uses the same data in central directory and local file data. 264 */ 265 @Override 266 public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException { 267 parseFromLocalFileData(buffer, offset, length); 268 } 269 270 /** 271 * Populate data from this array as if it was in local file data. 272 * 273 * @param data an array of bytes 274 * @param offset the start offset 275 * @param length the number of bytes in the array from offset 276 * @throws ZipException on error 277 */ 278 @Override 279 public void parseFromLocalFileData(final byte[] data, final int offset, final int length) throws ZipException { 280 if (length < MIN_SIZE) { 281 throw new ZipException("The length is too short, only " + length + " bytes, expected at least " + MIN_SIZE); 282 } 283 284 final long givenChecksum = ZipLong.getValue(data, offset); 285 final byte[] tmp = new byte[length - WORD]; 286 System.arraycopy(data, offset + WORD, tmp, 0, length - WORD); 287 crc.reset(); 288 crc.update(tmp); 289 final long realChecksum = crc.getValue(); 290 if (givenChecksum != realChecksum) { 291 throw new ZipException("Bad CRC checksum, expected " + Long.toHexString(givenChecksum) + " instead of " + Long.toHexString(realChecksum)); 292 } 293 294 final int newMode = ZipShort.getValue(tmp, 0); 295 // CheckStyle:MagicNumber OFF 296 final int linkArrayLength = (int) ZipLong.getValue(tmp, 2); 297 if (linkArrayLength < 0 || linkArrayLength > tmp.length - 10) { 298 throw new ZipException("Bad symbolic link name length " + linkArrayLength + " in ASI extra field"); 299 } 300 uid = ZipShort.getValue(tmp, 6); 301 gid = ZipShort.getValue(tmp, 8); 302 if (linkArrayLength == 0) { 303 link = ""; 304 } else { 305 final byte[] linkArray = new byte[linkArrayLength]; 306 System.arraycopy(tmp, 10, linkArray, 0, linkArrayLength); 307 link = new String(linkArray, Charset.defaultCharset()); // Uses default charset - see class Javadoc 308 } 309 // CheckStyle:MagicNumber ON 310 setDirectory((newMode & DIR_FLAG) != 0); 311 setMode(newMode); 312 } 313 314 /** 315 * Indicate whether this entry is a directory. 316 * 317 * @param dirFlag if true, this entry is a directory 318 */ 319 public void setDirectory(final boolean dirFlag) { 320 this.dirFlag = dirFlag; 321 mode = getMode(mode); 322 } 323 324 /** 325 * Sets the group id. 326 * 327 * @param gid the group id 328 */ 329 public void setGroupId(final int gid) { 330 this.gid = gid; 331 } 332 333 /** 334 * Indicate that this entry is a symbolic link to the given file name. 335 * 336 * @param name Name of the file this entry links to, empty String if it is not a symbolic link. 337 */ 338 public void setLinkedFile(final String name) { 339 link = name; 340 mode = getMode(mode); 341 } 342 343 /** 344 * File mode of this file. 345 * 346 * @param mode the file mode 347 */ 348 public void setMode(final int mode) { 349 this.mode = getMode(mode); 350 } 351 352 /** 353 * Sets the user id. 354 * 355 * @param uid the user id 356 */ 357 public void setUserId(final int uid) { 358 this.uid = uid; 359 } 360}