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 030 * link handling. 031 * 032 * <p>This class uses the ASi extra field in the format:</p> 033 * <pre> 034 * Value Size Description 035 * ----- ---- ----------- 036 * (Unix3) 0x756e Short tag for this extra block type 037 * TSize Short total data size for this block 038 * CRC Long CRC-32 of the remaining data 039 * Mode Short file permissions 040 * SizDev Long symlink'd size OR major/minor dev num 041 * UID Short user ID 042 * GID Short group ID 043 * (var.) variable symbolic link file name 044 * </pre> 045 * <p>taken from appnote.iz (Info-ZIP note, 981119) found at <a 046 * href="ftp://ftp.uu.net/pub/archiving/zip/doc/">ftp://ftp.uu.net/pub/archiving/zip/doc/</a></p> 047 * 048 * <p>Short is two bytes and Long is four bytes in big endian byte and 049 * word order, device numbers are currently not supported.</p> 050 * @NotThreadSafe 051 * 052 * <p>Since the documentation this class is based upon doesn't mention 053 * the character encoding of the file name at all, it is assumed that 054 * it uses the current platform's default encoding.</p> 055 */ 056public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { 057 058 private static final ZipShort HEADER_ID = new ZipShort(0x756E); 059 private static final int MIN_SIZE = WORD + SHORT + WORD + SHORT + SHORT; 060 /** 061 * Standard Unix stat(2) file mode. 062 */ 063 private int mode; 064 /** 065 * User ID. 066 */ 067 private int uid; 068 /** 069 * Group ID. 070 */ 071 private int gid; 072 /** 073 * File this entry points to, if it is a symbolic link. 074 * 075 * <p>empty string - if entry is not a symbolic link.</p> 076 */ 077 private String link = ""; 078 /** 079 * Is this an entry for a directory? 080 */ 081 private boolean dirFlag; 082 083 /** 084 * Instance used to calculate checksums. 085 */ 086 private CRC32 crc = new CRC32(); 087 088 /** Constructor for AsiExtraField. */ 089 public AsiExtraField() { 090 } 091 092 /** 093 * The Header-ID. 094 * @return the value for the header id for this extrafield 095 */ 096 @Override 097 public ZipShort getHeaderId() { 098 return HEADER_ID; 099 } 100 101 /** 102 * Length of the extra field in the local file data - without 103 * Header-ID or length specifier. 104 * @return a {@code ZipShort} for the length of the data of this extra field 105 */ 106 @Override 107 public ZipShort getLocalFileDataLength() { 108 // @formatter:off 109 return new ZipShort(WORD // CRC 110 + 2 // Mode 111 + WORD // SizDev 112 + 2 // UID 113 + 2 // GID 114 + getLinkedFile().getBytes(Charset.defaultCharset()).length); 115 // Uses default charset - see class Javadoc 116 // @formatter:on 117 } 118 119 /** 120 * Delegate to local file data. 121 * @return the centralDirectory length 122 */ 123 @Override 124 public ZipShort getCentralDirectoryLength() { 125 return getLocalFileDataLength(); 126 } 127 128 /** 129 * The actual data to put into local file data - without Header-ID 130 * or length specifier. 131 * @return get the data 132 */ 133 @Override 134 public byte[] getLocalFileDataData() { 135 // CRC will be added later 136 final byte[] data = new byte[getLocalFileDataLength().getValue() - WORD]; 137 System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2); 138 139 final byte[] linkArray = getLinkedFile().getBytes(Charset.defaultCharset()); // Uses default charset - see class Javadoc 140 // CheckStyle:MagicNumber OFF 141 System.arraycopy(ZipLong.getBytes(linkArray.length), 0, data, 2, WORD); 142 143 System.arraycopy(ZipShort.getBytes(getUserId()), 0, data, 6, 2); 144 System.arraycopy(ZipShort.getBytes(getGroupId()), 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, Charset.defaultCharset()); // 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 IllegalStateException(cnfe); //NOSONAR 346 } 347 } 348}