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