AsiExtraField.java

  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. import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT;
  21. import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;

  22. import java.nio.charset.Charset;
  23. import java.util.zip.CRC32;
  24. import java.util.zip.ZipException;

  25. /**
  26.  * Adds UNIX file permission and UID/GID fields as well as symbolic link handling.
  27.  *
  28.  * <p>
  29.  * This class uses the ASi extra field in the format:
  30.  * </p>
  31.  *
  32.  * <pre>
  33.  *         Value         Size            Description
  34.  *         -----         ----            -----------
  35.  * (Unix3) 0x756e        Short           tag for this extra block type
  36.  *         TSize         Short           total data size for this block
  37.  *         CRC           Long            CRC-32 of the remaining data
  38.  *         Mode          Short           file permissions
  39.  *         SizDev        Long            symlink'd size OR major/minor dev num
  40.  *         UID           Short           user ID
  41.  *         GID           Short           group ID
  42.  *         (var.)        variable        symbolic link file name
  43.  * </pre>
  44.  * <p>
  45.  * 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>
  46.  * </p>
  47.  *
  48.  * <p>
  49.  * Short is two bytes and Long is four bytes in big-endian byte and word order, device numbers are currently not supported.
  50.  * </p>
  51.  *
  52.  * @NotThreadSafe
  53.  *
  54.  *                <p>
  55.  *                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
  56.  *                the current platform's default encoding.
  57.  *                </p>
  58.  */
  59. public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable {

  60.     static final ZipShort HEADER_ID = new ZipShort(0x756E);
  61.     private static final int MIN_SIZE = WORD + SHORT + WORD + SHORT + SHORT;

  62.     /**
  63.      * Standard UNIX stat(2) file mode.
  64.      */
  65.     private int mode;
  66.     /**
  67.      * User ID.
  68.      */
  69.     private int uid;
  70.     /**
  71.      * Group ID.
  72.      */
  73.     private int gid;
  74.     /**
  75.      * File this entry points to, if it is a symbolic link.
  76.      *
  77.      * <p>
  78.      * empty string - if entry is not a symbolic link.
  79.      * </p>
  80.      */
  81.     private String link = "";
  82.     /**
  83.      * Is this an entry for a directory?
  84.      */
  85.     private boolean dirFlag;

  86.     /**
  87.      * Instance used to calculate checksums.
  88.      */
  89.     private CRC32 crc = new CRC32();

  90.     /** Constructor for AsiExtraField. */
  91.     public AsiExtraField() {
  92.     }

  93.     @Override
  94.     public Object clone() {
  95.         try {
  96.             final AsiExtraField cloned = (AsiExtraField) super.clone();
  97.             cloned.crc = new CRC32();
  98.             return cloned;
  99.         } catch (final CloneNotSupportedException cnfe) {
  100.             // impossible
  101.             throw new UnsupportedOperationException(cnfe); // NOSONAR
  102.         }
  103.     }

  104.     /**
  105.      * Delegate to local file data.
  106.      *
  107.      * @return the local file data
  108.      */
  109.     @Override
  110.     public byte[] getCentralDirectoryData() {
  111.         return getLocalFileDataData();
  112.     }

  113.     /**
  114.      * Delegate to local file data.
  115.      *
  116.      * @return the centralDirectory length
  117.      */
  118.     @Override
  119.     public ZipShort getCentralDirectoryLength() {
  120.         return getLocalFileDataLength();
  121.     }

  122.     /**
  123.      * Gets the group id.
  124.      *
  125.      * @return the group id
  126.      */
  127.     public int getGroupId() {
  128.         return gid;
  129.     }

  130.     /**
  131.      * The Header-ID.
  132.      *
  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.      * Name of linked file
  141.      *
  142.      * @return name of the file this entry links to if it is a symbolic link, the empty string otherwise.
  143.      */
  144.     public String getLinkedFile() {
  145.         return link;
  146.     }

  147.     /**
  148.      * The actual data to put into local file data - without Header-ID or length specifier.
  149.      *
  150.      * @return get the data
  151.      */
  152.     @Override
  153.     public byte[] getLocalFileDataData() {
  154.         // CRC will be added later
  155.         final byte[] data = new byte[getLocalFileDataLength().getValue() - WORD];
  156.         System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2);

  157.         final byte[] linkArray = getLinkedFile().getBytes(Charset.defaultCharset()); // Uses default charset - see class Javadoc
  158.         // CheckStyle:MagicNumber OFF
  159.         System.arraycopy(ZipLong.getBytes(linkArray.length), 0, data, 2, WORD);

  160.         System.arraycopy(ZipShort.getBytes(getUserId()), 0, data, 6, 2);
  161.         System.arraycopy(ZipShort.getBytes(getGroupId()), 0, data, 8, 2);

  162.         System.arraycopy(linkArray, 0, data, 10, linkArray.length);
  163.         // CheckStyle:MagicNumber ON

  164.         crc.reset();
  165.         crc.update(data);
  166.         final long checksum = crc.getValue();

  167.         final byte[] result = new byte[data.length + WORD];
  168.         System.arraycopy(ZipLong.getBytes(checksum), 0, result, 0, WORD);
  169.         System.arraycopy(data, 0, result, WORD, data.length);
  170.         return result;
  171.     }

  172.     /**
  173.      * Length of the extra field in the local file data - without Header-ID or length specifier.
  174.      *
  175.      * @return a {@code ZipShort} for the length of the data of this extra field
  176.      */
  177.     @Override
  178.     public ZipShort getLocalFileDataLength() {
  179.         // @formatter:off
  180.         return new ZipShort(WORD      // CRC
  181.                           + 2         // Mode
  182.                           + WORD      // SizDev
  183.                           + 2         // UID
  184.                           + 2         // GID
  185.                           + getLinkedFile().getBytes(Charset.defaultCharset()).length);
  186.                           // Uses default charset - see class Javadoc
  187.         // @formatter:on
  188.     }

  189.     /**
  190.      * File mode of this file.
  191.      *
  192.      * @return the file mode
  193.      */
  194.     public int getMode() {
  195.         return mode;
  196.     }

  197.     /**
  198.      * Gets the file mode for given permissions with the correct file type.
  199.      *
  200.      * @param mode the mode
  201.      * @return the type with the mode
  202.      */
  203.     protected int getMode(final int mode) {
  204.         int type = FILE_FLAG;
  205.         if (isLink()) {
  206.             type = LINK_FLAG;
  207.         } else if (isDirectory()) {
  208.             type = DIR_FLAG;
  209.         }
  210.         return type | mode & PERM_MASK;
  211.     }

  212.     /**
  213.      * Gets the user id.
  214.      *
  215.      * @return the user id
  216.      */
  217.     public int getUserId() {
  218.         return uid;
  219.     }

  220.     /**
  221.      * Is this entry a directory?
  222.      *
  223.      * @return true if this entry is a directory
  224.      */
  225.     public boolean isDirectory() {
  226.         return dirFlag && !isLink();
  227.     }

  228.     /**
  229.      * Is this entry a symbolic link?
  230.      *
  231.      * @return true if this is a symbolic link
  232.      */
  233.     public boolean isLink() {
  234.         return !getLinkedFile().isEmpty();
  235.     }

  236.     /**
  237.      * Doesn't do anything special since this class always uses the same data in central directory and local file data.
  238.      */
  239.     @Override
  240.     public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
  241.         parseFromLocalFileData(buffer, offset, length);
  242.     }

  243.     /**
  244.      * Populate data from this array as if it was in local file data.
  245.      *
  246.      * @param data   an array of bytes
  247.      * @param offset the start offset
  248.      * @param length the number of bytes in the array from offset
  249.      * @throws ZipException on error
  250.      */
  251.     @Override
  252.     public void parseFromLocalFileData(final byte[] data, final int offset, final int length) throws ZipException {
  253.         if (length < MIN_SIZE) {
  254.             throw new ZipException("The length is too short, only " + length + " bytes, expected at least " + MIN_SIZE);
  255.         }

  256.         final long givenChecksum = ZipLong.getValue(data, offset);
  257.         final byte[] tmp = new byte[length - WORD];
  258.         System.arraycopy(data, offset + WORD, tmp, 0, length - WORD);
  259.         crc.reset();
  260.         crc.update(tmp);
  261.         final long realChecksum = crc.getValue();
  262.         if (givenChecksum != realChecksum) {
  263.             throw new ZipException("Bad CRC checksum, expected " + Long.toHexString(givenChecksum) + " instead of " + Long.toHexString(realChecksum));
  264.         }

  265.         final int newMode = ZipShort.getValue(tmp, 0);
  266.         // CheckStyle:MagicNumber OFF
  267.         final int linkArrayLength = (int) ZipLong.getValue(tmp, 2);
  268.         if (linkArrayLength < 0 || linkArrayLength > tmp.length - 10) {
  269.             throw new ZipException("Bad symbolic link name length " + linkArrayLength + " in ASI extra field");
  270.         }
  271.         uid = ZipShort.getValue(tmp, 6);
  272.         gid = ZipShort.getValue(tmp, 8);
  273.         if (linkArrayLength == 0) {
  274.             link = "";
  275.         } else {
  276.             final byte[] linkArray = new byte[linkArrayLength];
  277.             System.arraycopy(tmp, 10, linkArray, 0, linkArrayLength);
  278.             link = new String(linkArray, Charset.defaultCharset()); // Uses default charset - see class Javadoc
  279.         }
  280.         // CheckStyle:MagicNumber ON
  281.         setDirectory((newMode & DIR_FLAG) != 0);
  282.         setMode(newMode);
  283.     }

  284.     /**
  285.      * Indicate whether this entry is a directory.
  286.      *
  287.      * @param dirFlag if true, this entry is a directory
  288.      */
  289.     public void setDirectory(final boolean dirFlag) {
  290.         this.dirFlag = dirFlag;
  291.         mode = getMode(mode);
  292.     }

  293.     /**
  294.      * Sets the group id.
  295.      *
  296.      * @param gid the group id
  297.      */
  298.     public void setGroupId(final int gid) {
  299.         this.gid = gid;
  300.     }

  301.     /**
  302.      * Indicate that this entry is a symbolic link to the given file name.
  303.      *
  304.      * @param name Name of the file this entry links to, empty String if it is not a symbolic link.
  305.      */
  306.     public void setLinkedFile(final String name) {
  307.         link = name;
  308.         mode = getMode(mode);
  309.     }

  310.     /**
  311.      * File mode of this file.
  312.      *
  313.      * @param mode the file mode
  314.      */
  315.     public void setMode(final int mode) {
  316.         this.mode = getMode(mode);
  317.     }

  318.     /**
  319.      * Sets the user id.
  320.      *
  321.      * @param uid the user id
  322.      */
  323.     public void setUserId(final int uid) {
  324.         this.uid = uid;
  325.     }
  326. }