View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.archivers.zip;
20  
21  import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT;
22  import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
23  
24  import java.nio.charset.Charset;
25  import java.util.zip.CRC32;
26  import java.util.zip.ZipException;
27  
28  /**
29   * Adds Unix file permission and UID/GID fields as well as symbolic link handling.
30   *
31   * <p>
32   * This class uses the ASi extra field in the format:
33   * </p>
34   *
35   * <pre>
36   *         Value         Size            Description
37   *         -----         ----            -----------
38   * (Unix3) 0x756e        Short           tag for this extra block type
39   *         TSize         Short           total data size for this block
40   *         CRC           Long            CRC-32 of the remaining data
41   *         Mode          Short           file permissions
42   *         SizDev        Long            symlink'd size OR major/minor dev num
43   *         UID           Short           user ID
44   *         GID           Short           group ID
45   *         (var.)        variable        symbolic link file name
46   * </pre>
47   * <p>
48   * 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>
49   * </p>
50   *
51   * <p>
52   * Short is two bytes and Long is four bytes in big-endian byte and word order, device numbers are currently not supported.
53   * </p>
54   *
55   * @NotThreadSafe
56   *
57   *                <p>
58   *                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
59   *                the current platform's default encoding.
60   *                </p>
61   */
62  public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable {
63  
64      static final ZipShort HEADER_ID = new ZipShort(0x756E);
65      private static final int MIN_SIZE = WORD + SHORT + WORD + SHORT + SHORT;
66  
67      /**
68       * Standard Unix stat(2) file mode.
69       */
70      private int mode;
71      /**
72       * User ID.
73       */
74      private int uid;
75      /**
76       * Group ID.
77       */
78      private int gid;
79      /**
80       * File this entry points to, if it is a symbolic link.
81       *
82       * <p>
83       * empty string - if entry is not a symbolic link.
84       * </p>
85       */
86      private String link = "";
87      /**
88       * Is this an entry for a directory?
89       */
90      private boolean dirFlag;
91  
92      /**
93       * Instance used to calculate checksums.
94       */
95      private CRC32 crc = new CRC32();
96  
97      /** Constructor for AsiExtraField. */
98      public AsiExtraField() {
99      }
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 }