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 @Override 093 public Object clone() { 094 try { 095 final AsiExtraField cloned = (AsiExtraField) super.clone(); 096 cloned.crc = new CRC32(); 097 return cloned; 098 } catch (final CloneNotSupportedException cnfe) { 099 // impossible 100 throw new UnsupportedOperationException(cnfe); //NOSONAR 101 } 102 } 103 104 /** 105 * Delegate to local file data. 106 * @return the local file data 107 */ 108 @Override 109 public byte[] getCentralDirectoryData() { 110 return getLocalFileDataData(); 111 } 112 113 /** 114 * Delegate to local file data. 115 * @return the centralDirectory length 116 */ 117 @Override 118 public ZipShort getCentralDirectoryLength() { 119 return getLocalFileDataLength(); 120 } 121 122 /** 123 * Get the group id. 124 * @return the group id 125 */ 126 public int getGroupId() { 127 return gid; 128 } 129 130 /** 131 * The Header-ID. 132 * @return the value for the header id for this extrafield 133 */ 134 @Override 135 public ZipShort getHeaderId() { 136 return HEADER_ID; 137 } 138 139 /** 140 * Name of linked file 141 * 142 * @return name of the file this entry links to if it is a 143 * symbolic link, the empty string otherwise. 144 */ 145 public String getLinkedFile() { 146 return link; 147 } 148 149 /** 150 * The actual data to put into local file data - without Header-ID 151 * or length specifier. 152 * @return get the data 153 */ 154 @Override 155 public byte[] getLocalFileDataData() { 156 // CRC will be added later 157 final byte[] data = new byte[getLocalFileDataLength().getValue() - WORD]; 158 System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2); 159 160 final byte[] linkArray = getLinkedFile().getBytes(Charset.defaultCharset()); // Uses default charset - see class Javadoc 161 // CheckStyle:MagicNumber OFF 162 System.arraycopy(ZipLong.getBytes(linkArray.length), 0, data, 2, WORD); 163 164 System.arraycopy(ZipShort.getBytes(getUserId()), 0, data, 6, 2); 165 System.arraycopy(ZipShort.getBytes(getGroupId()), 0, data, 8, 2); 166 167 System.arraycopy(linkArray, 0, data, 10, linkArray.length); 168 // CheckStyle:MagicNumber ON 169 170 crc.reset(); 171 crc.update(data); 172 final long checksum = crc.getValue(); 173 174 final byte[] result = new byte[data.length + WORD]; 175 System.arraycopy(ZipLong.getBytes(checksum), 0, result, 0, WORD); 176 System.arraycopy(data, 0, result, WORD, data.length); 177 return result; 178 } 179 180 /** 181 * Length of the extra field in the local file data - without 182 * Header-ID or length specifier. 183 * @return a {@code ZipShort} for the length of the data of this extra field 184 */ 185 @Override 186 public ZipShort getLocalFileDataLength() { 187 // @formatter:off 188 return new ZipShort(WORD // CRC 189 + 2 // Mode 190 + WORD // SizDev 191 + 2 // UID 192 + 2 // GID 193 + getLinkedFile().getBytes(Charset.defaultCharset()).length); 194 // Uses default charset - see class Javadoc 195 // @formatter:on 196 } 197 198 /** 199 * File mode of this file. 200 * @return the file mode 201 */ 202 public int getMode() { 203 return mode; 204 } 205 206 /** 207 * Get the file mode for given permissions with the correct file type. 208 * @param mode the mode 209 * @return the type with the mode 210 */ 211 protected int getMode(final int mode) { 212 int type = FILE_FLAG; 213 if (isLink()) { 214 type = LINK_FLAG; 215 } else if (isDirectory()) { 216 type = DIR_FLAG; 217 } 218 return type | (mode & PERM_MASK); 219 } 220 221 /** 222 * Get the user id. 223 * @return the user id 224 */ 225 public int getUserId() { 226 return uid; 227 } 228 229 /** 230 * Is this entry a directory? 231 * @return true if this entry is a directory 232 */ 233 public boolean isDirectory() { 234 return dirFlag && !isLink(); 235 } 236 237 /** 238 * Is this entry a symbolic link? 239 * @return true if this is a symbolic link 240 */ 241 public boolean isLink() { 242 return !getLinkedFile().isEmpty(); 243 } 244 245 /** 246 * Doesn't do anything special since this class always uses the 247 * same data in central directory and local file data. 248 */ 249 @Override 250 public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, 251 final int length) 252 throws ZipException { 253 parseFromLocalFileData(buffer, offset, length); 254 } 255 256 /** 257 * Populate data from this array as if it was in local file data. 258 * @param data an array of bytes 259 * @param offset the start offset 260 * @param length the number of bytes in the array from offset 261 * @throws ZipException on error 262 */ 263 @Override 264 public void parseFromLocalFileData(final byte[] data, final int offset, final int length) 265 throws ZipException { 266 if (length < MIN_SIZE) { 267 throw new ZipException("The length is too short, only " 268 + length + " bytes, expected at least " + MIN_SIZE); 269 } 270 271 final long givenChecksum = ZipLong.getValue(data, offset); 272 final byte[] tmp = new byte[length - WORD]; 273 System.arraycopy(data, offset + WORD, tmp, 0, length - WORD); 274 crc.reset(); 275 crc.update(tmp); 276 final long realChecksum = crc.getValue(); 277 if (givenChecksum != realChecksum) { 278 throw new ZipException("Bad CRC checksum, expected " 279 + Long.toHexString(givenChecksum) 280 + " instead of " 281 + Long.toHexString(realChecksum)); 282 } 283 284 final int newMode = ZipShort.getValue(tmp, 0); 285 // CheckStyle:MagicNumber OFF 286 final int linkArrayLength = (int) ZipLong.getValue(tmp, 2); 287 if (linkArrayLength < 0 || linkArrayLength > tmp.length - 10) { 288 throw new ZipException("Bad symbolic link name length " + linkArrayLength 289 + " in ASI extra field"); 290 } 291 uid = ZipShort.getValue(tmp, 6); 292 gid = ZipShort.getValue(tmp, 8); 293 if (linkArrayLength == 0) { 294 link = ""; 295 } else { 296 final byte[] linkArray = new byte[linkArrayLength]; 297 System.arraycopy(tmp, 10, linkArray, 0, linkArrayLength); 298 link = new String(linkArray, Charset.defaultCharset()); // Uses default charset - see class Javadoc 299 } 300 // CheckStyle:MagicNumber ON 301 setDirectory((newMode & DIR_FLAG) != 0); 302 setMode(newMode); 303 } 304 305 /** 306 * Indicate whether this entry is a directory. 307 * @param dirFlag if true, this entry is a directory 308 */ 309 public void setDirectory(final boolean dirFlag) { 310 this.dirFlag = dirFlag; 311 mode = getMode(mode); 312 } 313 314 /** 315 * Set the group id. 316 * @param gid the group id 317 */ 318 public void setGroupId(final int gid) { 319 this.gid = gid; 320 } 321 322 /** 323 * Indicate that this entry is a symbolic link to the given file name. 324 * 325 * @param name Name of the file this entry links to, empty String 326 * if it is not a symbolic link. 327 */ 328 public void setLinkedFile(final String name) { 329 link = name; 330 mode = getMode(mode); 331 } 332 333 /** 334 * File mode of this file. 335 * @param mode the file mode 336 */ 337 public void setMode(final int mode) { 338 this.mode = getMode(mode); 339 } 340 341 /** 342 * Set the user id. 343 * @param uid the user id 344 */ 345 public void setUserId(final int uid) { 346 this.uid = uid; 347 } 348}