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