    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     */
    019    package org.apache.commons.compress.archivers.cpio;
    020
    021    import java.io.EOFException;
    022    import java.io.IOException;
    023    import java.io.InputStream;
    024
    025    import org.apache.commons.compress.archivers.ArchiveEntry;
    026    import org.apache.commons.compress.archivers.ArchiveInputStream;
    027    import org.apache.commons.compress.utils.ArchiveUtils;
    028
    029    /**
    030     * CPIOArchiveInputStream is a stream for reading cpio streams. All formats of
    031     * cpio are supported (old ascii, old binary, new portable format and the new
    032     * portable format with crc).
    033     * <p/>
    034     * <p/>
    035     * The stream can be read by extracting a cpio entry (containing all
    036     * informations about a entry) and afterwards reading from the stream the file
    037     * specified by the entry.
    038     * <p/>
    039     * <code><pre>
    040     * CPIOArchiveInputStream cpioIn = new CPIOArchiveInputStream(
    041     *         new FileInputStream(new File(&quot;test.cpio&quot;)));
    042     * CPIOArchiveEntry cpioEntry;
    043     * <p/>
    044     * while ((cpioEntry = cpioIn.getNextEntry()) != null) {
    045     *     System.out.println(cpioEntry.getName());
    046     *     int tmp;
    047     *     StringBuffer buf = new StringBuffer();
    048     *     while ((tmp = cpIn.read()) != -1) {
    049     *         buf.append((char) tmp);
    050     *     }
    051     *     System.out.println(buf.toString());
    052     * }
    053     * cpioIn.close();
    054     * </pre></code>
    055     * <p/>
    056     * Note: This implementation should be compatible to cpio 2.5
    057     *
    058     * This class uses mutable fields and is not considered to be threadsafe.
    059     *
    060     * Based on code from the jRPM project (jrpm.sourceforge.net)
    061     */
    062
    063    public class CpioArchiveInputStream extends ArchiveInputStream implements
    064            CpioConstants {
    065
    066        private boolean closed = false;
    067
    068        private CpioArchiveEntry entry;
    069
    070        private long entryBytesRead = 0;
    071
    072        private boolean entryEOF = false;
    073
    074        private final byte tmpbuf[] = new byte[4096];
    075
    076        private long crc = 0;
    077
    078        private final InputStream in;
    079
    080        /**
    081         * Construct the cpio input stream
    082         *
    083         * @param in
    084         *            The cpio stream
    085         */
    086        public CpioArchiveInputStream(final InputStream in) {
    087            this.in = in;
    088        }
    089
    090        /**
    091         * Returns 0 after EOF has reached for the current entry data, otherwise
    092         * always return 1.
    093         * <p/>
    094         * Programs should not count on this method to return the actual number of
    095         * bytes that could be read without blocking.
    096         *
    097         * @return 1 before EOF and 0 after EOF has reached for current entry.
    098         * @throws IOException
    099         *             if an I/O error has occurred or if a CPIO file error has
    100         *             occurred
    101         */
    102        @Override
    103        public int available() throws IOException {
    104            ensureOpen();
    105            if (this.entryEOF) {
    106                return 0;
    107            }
    108            return 1;
    109        }
    110
    111        /**
    112         * Closes the CPIO input stream.
    113         *
    114         * @throws IOException
    115         *             if an I/O error has occurred
    116         */
    117        @Override
    118        public void close() throws IOException {
    119            if (!this.closed) {
    120                in.close();
    121                this.closed = true;
    122            }
    123        }
    124
    125        /**
    126         * Closes the current CPIO entry and positions the stream for reading the
    127         * next entry.
    128         *
    129         * @throws IOException
    130         *             if an I/O error has occurred or if a CPIO file error has
    131         *             occurred
    132         */
    133        private void closeEntry() throws IOException {
    134            ensureOpen();
    135            while (read(this.tmpbuf, 0, this.tmpbuf.length) != -1) { // NOPMD
    136                // do nothing
    137            }
    138
    139            this.entryEOF = true;
    140        }
    141
    142        /**
    143         * Check to make sure that this stream has not been closed
    144         *
    145         * @throws IOException
    146         *             if the stream is already closed
    147         */
    148        private void ensureOpen() throws IOException {
    149            if (this.closed) {
    150                throw new IOException("Stream closed");
    151            }
    152        }
    153
    154        /**
    155         * Reads the next CPIO file entry and positions stream at the beginning of
    156         * the entry data.
    157         *
    158         * @return the CPIOArchiveEntry just read
    159         * @throws IOException
    160         *             if an I/O error has occurred or if a CPIO file error has
    161         *             occurred
    162         */
    163        public CpioArchiveEntry getNextCPIOEntry() throws IOException {
    164            ensureOpen();
    165            if (this.entry != null) {
    166                closeEntry();
    167            }
    168            byte magic[] = new byte[2];
    169            readFully(magic, 0, magic.length);
    170            if (CpioUtil.byteArray2long(magic, false) == MAGIC_OLD_BINARY) {
    171                this.entry = readOldBinaryEntry(false);
    172            } else if (CpioUtil.byteArray2long(magic, true) == MAGIC_OLD_BINARY) {
    173                this.entry = readOldBinaryEntry(true);
    174            } else {
    175                byte more_magic[] = new byte[4];
    176                readFully(more_magic, 0, more_magic.length);
    177                byte tmp[] = new byte[6];
    178                System.arraycopy(magic, 0, tmp, 0, magic.length);
    179                System.arraycopy(more_magic, 0, tmp, magic.length,
    180                        more_magic.length);
    181                String magicString = ArchiveUtils.toAsciiString(tmp);
    182                if (magicString.equals(MAGIC_NEW)) {
    183                    this.entry = readNewEntry(false);
    184                } else if (magicString.equals(MAGIC_NEW_CRC)) {
    185                    this.entry = readNewEntry(true);
    186                } else if (magicString.equals(MAGIC_OLD_ASCII)) {
    187                    this.entry = readOldAsciiEntry();
    188                } else {
    189                    throw new IOException("Unknown magic [" + magicString + "]. Occured at byte: " + getBytesRead());
    190                }
    191            }
    192
    193            this.entryBytesRead = 0;
    194            this.entryEOF = false;
    195            this.crc = 0;
    196
    197            if (this.entry.getName().equals(CPIO_TRAILER)) {
    198                this.entryEOF = true;
    199                return null;
    200            }
    201            return this.entry;
    202        }
    203
    204        private void skip(int bytes) throws IOException{
    205            final byte[] buff = new byte[4]; // Cannot be more than 3 bytes
    206            if (bytes > 0) {
    207                readFully(buff, 0, bytes);
    208            }
    209        }
    210
    211        /**
    212         * Reads from the current CPIO entry into an array of bytes. Blocks until
    213         * some input is available.
    214         *
    215         * @param b
    216         *            the buffer into which the data is read
    217         * @param off
    218         *            the start offset of the data
    219         * @param len
    220         *            the maximum number of bytes read
    221         * @return the actual number of bytes read, or -1 if the end of the entry is
    222         *         reached
    223         * @throws IOException
    224         *             if an I/O error has occurred or if a CPIO file error has
    225         *             occurred
    226         */
    227        @Override
    228        public int read(final byte[] b, final int off, final int len)
    229                throws IOException {
    230            ensureOpen();
    231            if (off < 0 || len < 0 || off > b.length - len) {
    232                throw new IndexOutOfBoundsException();
    233            } else if (len == 0) {
    234                return 0;
    235            }
    236
    237            if (this.entry == null || this.entryEOF) {
    238                return -1;
    239            }
    240            if (this.entryBytesRead == this.entry.getSize()) {
    241                skip(entry.getDataPadCount());
    242                this.entryEOF = true;
    243                if (this.entry.getFormat() == FORMAT_NEW_CRC
    244                    && this.crc != this.entry.getChksum()) {
    245                    throw new IOException("CRC Error. Occured at byte: "
    246                                          + getBytesRead());
    247                }
    248                return -1; // EOF for this entry
    249            }
    250            int tmplength = (int) Math.min(len, this.entry.getSize()
    251                    - this.entryBytesRead);
    252            if (tmplength < 0) {
    253                return -1;
    254            }
    255
    256            int tmpread = readFully(b, off, tmplength);
    257            if (this.entry.getFormat() == FORMAT_NEW_CRC) {
    258                for (int pos = 0; pos < tmpread; pos++) {
    259                    this.crc += b[pos] & 0xFF;
    260                }
    261            }
    262            this.entryBytesRead += tmpread;
    263
    264            return tmpread;
    265        }
    266
    267        private final int readFully(final byte[] b, final int off, final int len)
    268                throws IOException {
    269            if (len < 0) {
    270                throw new IndexOutOfBoundsException();
    271            }
    272            int n = 0;
    273            while (n < len) {
    274                int count = this.in.read(b, off + n, len - n);
    275                count(count);
    276                if (count < 0) {
    277                    throw new EOFException();
    278                }
    279                n += count;
    280            }
    281            return n;
    282        }
    283
    284        private long readBinaryLong(final int length, final boolean swapHalfWord)
    285                throws IOException {
    286            byte tmp[] = new byte[length];
    287            readFully(tmp, 0, tmp.length);
    288            return CpioUtil.byteArray2long(tmp, swapHalfWord);
    289        }
    290
    291        private long readAsciiLong(final int length, final int radix)
    292                throws IOException {
    293            byte tmpBuffer[] = new byte[length];
    294            readFully(tmpBuffer, 0, tmpBuffer.length);
    295            return Long.parseLong(ArchiveUtils.toAsciiString(tmpBuffer), radix);
    296        }
    297
    298        private CpioArchiveEntry readNewEntry(final boolean hasCrc)
    299                throws IOException {
    300            CpioArchiveEntry ret;
    301            if (hasCrc) {
    302                ret = new CpioArchiveEntry(FORMAT_NEW_CRC);
    303            } else {
    304                ret = new CpioArchiveEntry(FORMAT_NEW);
    305            }
    306
    307            ret.setInode(readAsciiLong(8, 16));
    308            long mode = readAsciiLong(8, 16);
    309            if (mode != 0){ // mode is initialised to 0
    310                ret.setMode(mode);
    311            }
    312            ret.setUID(readAsciiLong(8, 16));
    313            ret.setGID(readAsciiLong(8, 16));
    314            ret.setNumberOfLinks(readAsciiLong(8, 16));
    315            ret.setTime(readAsciiLong(8, 16));
    316            ret.setSize(readAsciiLong(8, 16));
    317            ret.setDeviceMaj(readAsciiLong(8, 16));
    318            ret.setDeviceMin(readAsciiLong(8, 16));
    319            ret.setRemoteDeviceMaj(readAsciiLong(8, 16));
    320            ret.setRemoteDeviceMin(readAsciiLong(8, 16));
    321            long namesize = readAsciiLong(8, 16);
    322            ret.setChksum(readAsciiLong(8, 16));
    323            String name = readCString((int) namesize);
    324            ret.setName(name);
    325            if (mode == 0 && !name.equals(CPIO_TRAILER)){
    326                throw new IOException("Mode 0 only allowed in the trailer. Found entry name: "+name + " Occured at byte: " + getBytesRead());
    327            }
    328            skip(ret.getHeaderPadCount());
    329
    330            return ret;
    331        }
    332
    333        private CpioArchiveEntry readOldAsciiEntry() throws IOException {
    334            CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_ASCII);
    335
    336            ret.setDevice(readAsciiLong(6, 8));
    337            ret.setInode(readAsciiLong(6, 8));
    338            final long mode = readAsciiLong(6, 8);
    339            if (mode != 0) {
    340                ret.setMode(mode);
    341            }
    342            ret.setUID(readAsciiLong(6, 8));
    343            ret.setGID(readAsciiLong(6, 8));
    344            ret.setNumberOfLinks(readAsciiLong(6, 8));
    345            ret.setRemoteDevice(readAsciiLong(6, 8));
    346            ret.setTime(readAsciiLong(11, 8));
    347            long namesize = readAsciiLong(6, 8);
    348            ret.setSize(readAsciiLong(11, 8));
    349            final String name = readCString((int) namesize);
    350            ret.setName(name);
    351            if (mode == 0 && !name.equals(CPIO_TRAILER)){
    352                throw new IOException("Mode 0 only allowed in the trailer. Found entry: "+ name + " Occured at byte: " + getBytesRead());
    353            }
    354
    355            return ret;
    356        }
    357
    358        private CpioArchiveEntry readOldBinaryEntry(final boolean swapHalfWord)
    359                throws IOException {
    360            CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_BINARY);
    361
    362            ret.setDevice(readBinaryLong(2, swapHalfWord));
    363            ret.setInode(readBinaryLong(2, swapHalfWord));
    364            final long mode = readBinaryLong(2, swapHalfWord);
    365            if (mode != 0){
    366                ret.setMode(mode);
    367            }
    368            ret.setUID(readBinaryLong(2, swapHalfWord));
    369            ret.setGID(readBinaryLong(2, swapHalfWord));
    370            ret.setNumberOfLinks(readBinaryLong(2, swapHalfWord));
    371            ret.setRemoteDevice(readBinaryLong(2, swapHalfWord));
    372            ret.setTime(readBinaryLong(4, swapHalfWord));
    373            long namesize = readBinaryLong(2, swapHalfWord);
    374            ret.setSize(readBinaryLong(4, swapHalfWord));
    375            final String name = readCString((int) namesize);
    376            ret.setName(name);
    377            if (mode == 0 && !name.equals(CPIO_TRAILER)){
    378                throw new IOException("Mode 0 only allowed in the trailer. Found entry: "+name + "Occured at byte: " + getBytesRead());
    379            }
    380            skip(ret.getHeaderPadCount());
    381
    382            return ret;
    383        }
    384
    385        private String readCString(final int length) throws IOException {
    386            byte tmpBuffer[] = new byte[length];
    387            readFully(tmpBuffer, 0, tmpBuffer.length);
    388            return new String(tmpBuffer, 0, tmpBuffer.length - 1);
    389        }
    390
    391        /**
    392         * Skips specified number of bytes in the current CPIO entry.
    393         *
    394         * @param n
    395         *            the number of bytes to skip
    396         * @return the actual number of bytes skipped
    397         * @throws IOException
    398         *             if an I/O error has occurred
    399         * @throws IllegalArgumentException
    400         *             if n < 0
    401         */
    402        @Override
    403        public long skip(final long n) throws IOException {
    404            if (n < 0) {
    405                throw new IllegalArgumentException("negative skip length");
    406            }
    407            ensureOpen();
    408            int max = (int) Math.min(n, Integer.MAX_VALUE);
    409            int total = 0;
    410
    411            while (total < max) {
    412                int len = max - total;
    413                if (len > this.tmpbuf.length) {
    414                    len = this.tmpbuf.length;
    415                }
    416                len = read(this.tmpbuf, 0, len);
    417                if (len == -1) {
    418                    this.entryEOF = true;
    419                    break;
    420                }
    421                total += len;
    422            }
    423            return total;
    424        }
    425
    426        /** {@inheritDoc} */
    427        @Override
    428        public ArchiveEntry getNextEntry() throws IOException {
    429            return getNextCPIOEntry();
    430        }
    431
    432        /**
    433         * Checks if the signature matches one of the following magic values:
    434         *
    435         * Strings:
    436         *
    437         * "070701" - MAGIC_NEW
    438         * "070702" - MAGIC_NEW_CRC
    439         * "070707" - MAGIC_OLD_ASCII
    440         *
    441         * Octal Binary value:
    442         *
    443         * 070707 - MAGIC_OLD_BINARY (held as a short) = 0x71C7 or 0xC771
    444         */
    445        public static boolean matches(byte[] signature, int length) {
    446            if (length < 6) {
    447                return false;
    448            }
    449
    450            // Check binary values
    451            if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7) {
    452                return true;
    453            }
    454            if (signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) {
    455                return true;
    456            }
    457
    458            // Check Ascii (String) values
    459            // 3037 3037 30nn
    460            if (signature[0] != 0x30) {
    461                return false;
    462            }
    463            if (signature[1] != 0x37) {
    464                return false;
    465            }
    466            if (signature[2] != 0x30) {
    467                return false;
    468            }
    469            if (signature[3] != 0x37) {
    470                return false;
    471            }
    472            if (signature[4] != 0x30) {
    473                return false;
    474            }
    475            // Check last byte
    476            if (signature[5] == 0x31) {
    477                return true;
    478            }
    479            if (signature[5] == 0x32) {
    480                return true;
    481            }
    482            if (signature[5] == 0x37) {
    483                return true;
    484            }
    485
    486            return false;
    487        }
    488    }