    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        public int available() throws IOException {
    103            ensureOpen();
    104            if (this.entryEOF) {
    105                return 0;
    106            }
    107            return 1;
    108        }
    109
    110        /**
    111         * Closes the CPIO input stream.
    112         *
    113         * @throws IOException
    114         *             if an I/O error has occurred
    115         */
    116        public void close() throws IOException {
    117            if (!this.closed) {
    118                in.close();
    119                this.closed = true;
    120            }
    121        }
    122
    123        /**
    124         * Closes the current CPIO entry and positions the stream for reading the
    125         * next entry.
    126         *
    127         * @throws IOException
    128         *             if an I/O error has occurred or if a CPIO file error has
    129         *             occurred
    130         */
    131        private void closeEntry() throws IOException {
    132            ensureOpen();
    133            while (read(this.tmpbuf, 0, this.tmpbuf.length) != -1) {
    134                // do nothing
    135            }
    136
    137            this.entryEOF = true;
    138        }
    139
    140        /**
    141         * Check to make sure that this stream has not been closed
    142         *
    143         * @throws IOException
    144         *             if the stream is already closed
    145         */
    146        private void ensureOpen() throws IOException {
    147            if (this.closed) {
    148                throw new IOException("Stream closed");
    149            }
    150        }
    151
    152        /**
    153         * Reads the next CPIO file entry and positions stream at the beginning of
    154         * the entry data.
    155         *
    156         * @return the CPIOArchiveEntry just read
    157         * @throws IOException
    158         *             if an I/O error has occurred or if a CPIO file error has
    159         *             occurred
    160         */
    161        public CpioArchiveEntry getNextCPIOEntry() throws IOException {
    162            ensureOpen();
    163            if (this.entry != null) {
    164                closeEntry();
    165            }
    166            byte magic[] = new byte[2];
    167            readFully(magic, 0, magic.length);
    168            if (CpioUtil.byteArray2long(magic, false) == MAGIC_OLD_BINARY) {
    169                this.entry = readOldBinaryEntry(false);
    170            } else if (CpioUtil.byteArray2long(magic, true) == MAGIC_OLD_BINARY) {
    171                this.entry = readOldBinaryEntry(true);
    172            } else {
    173                byte more_magic[] = new byte[4];
    174                readFully(more_magic, 0, more_magic.length);
    175                byte tmp[] = new byte[6];
    176                System.arraycopy(magic, 0, tmp, 0, magic.length);
    177                System.arraycopy(more_magic, 0, tmp, magic.length,
    178                        more_magic.length);
    179                String magicString = ArchiveUtils.toAsciiString(tmp);
    180                if (magicString.equals(MAGIC_NEW)) {
    181                    this.entry = readNewEntry(false);
    182                } else if (magicString.equals(MAGIC_NEW_CRC)) {
    183                    this.entry = readNewEntry(true);
    184                } else if (magicString.equals(MAGIC_OLD_ASCII)) {
    185                    this.entry = readOldAsciiEntry();
    186                } else {
    187                    throw new IOException("Unknown magic [" + magicString + "]. Occured at byte: " + getBytesRead());
    188                }
    189            }
    190
    191            this.entryBytesRead = 0;
    192            this.entryEOF = false;
    193            this.crc = 0;
    194
    195            if (this.entry.getName().equals(CPIO_TRAILER)) {
    196                this.entryEOF = true;
    197                return null;
    198            }
    199            return this.entry;
    200        }
    201
    202        private void skip(int bytes) throws IOException{
    203            final byte[] buff = new byte[4]; // Cannot be more than 3 bytes
    204            if (bytes > 0) {
    205                readFully(buff, 0, bytes);
    206            }
    207        }
    208
    209        /**
    210         * Reads from the current CPIO entry into an array of bytes. Blocks until
    211         * some input is available.
    212         *
    213         * @param b
    214         *            the buffer into which the data is read
    215         * @param off
    216         *            the start offset of the data
    217         * @param len
    218         *            the maximum number of bytes read
    219         * @return the actual number of bytes read, or -1 if the end of the entry is
    220         *         reached
    221         * @throws IOException
    222         *             if an I/O error has occurred or if a CPIO file error has
    223         *             occurred
    224         */
    225        public int read(final byte[] b, final int off, final int len)
    226                throws IOException {
    227            ensureOpen();
    228            if (off < 0 || len < 0 || off > b.length - len) {
    229                throw new IndexOutOfBoundsException();
    230            } else if (len == 0) {
    231                return 0;
    232            }
    233
    234            if (this.entry == null || this.entryEOF) {
    235                return -1;
    236            }
    237            if (this.entryBytesRead == this.entry.getSize()) {
    238                skip(entry.getDataPadCount());
    239                this.entryEOF = true;
    240                if (this.entry.getFormat() == FORMAT_NEW_CRC) {
    241                    if (this.crc != this.entry.getChksum()) {
    242                        throw new IOException("CRC Error. Occured at byte: " + getBytesRead());
    243                    }
    244                }
    245                return -1; // EOF for this entry
    246            }
    247            int tmplength = (int) Math.min(len, this.entry.getSize()
    248                    - this.entryBytesRead);
    249            if (tmplength < 0) {
    250                return -1;
    251            }
    252
    253            int tmpread = readFully(b, off, tmplength);
    254            if (this.entry.getFormat() == FORMAT_NEW_CRC) {
    255                for (int pos = 0; pos < tmpread; pos++) {
    256                    this.crc += b[pos] & 0xFF;
    257                }
    258            }
    259            this.entryBytesRead += tmpread;
    260
    261            return tmpread;
    262        }
    263
    264        private final int readFully(final byte[] b, final int off, final int len)
    265                throws IOException {
    266            if (len < 0) {
    267                throw new IndexOutOfBoundsException();
    268            }
    269            int n = 0;
    270            while (n < len) {
    271                int count = this.in.read(b, off + n, len - n);
    272                count(count);
    273                if (count < 0) {
    274                    throw new EOFException();
    275                }
    276                n += count;
    277            }
    278            return n;
    279        }
    280
    281        private long readBinaryLong(final int length, final boolean swapHalfWord)
    282                throws IOException {
    283            byte tmp[] = new byte[length];
    284            readFully(tmp, 0, tmp.length);
    285            return CpioUtil.byteArray2long(tmp, swapHalfWord);
    286        }
    287
    288        private long readAsciiLong(final int length, final int radix)
    289                throws IOException {
    290            byte tmpBuffer[] = new byte[length];
    291            readFully(tmpBuffer, 0, tmpBuffer.length);
    292            return Long.parseLong(ArchiveUtils.toAsciiString(tmpBuffer), radix);
    293        }
    294
    295        private CpioArchiveEntry readNewEntry(final boolean hasCrc)
    296                throws IOException {
    297            CpioArchiveEntry ret;
    298            if (hasCrc) {
    299                ret = new CpioArchiveEntry(FORMAT_NEW_CRC);
    300            } else {
    301                ret = new CpioArchiveEntry(FORMAT_NEW);
    302            }
    303
    304            ret.setInode(readAsciiLong(8, 16));
    305            long mode = readAsciiLong(8, 16);
    306            if (mode != 0){ // mode is initialised to 0
    307                ret.setMode(mode);
    308            }
    309            ret.setUID(readAsciiLong(8, 16));
    310            ret.setGID(readAsciiLong(8, 16));
    311            ret.setNumberOfLinks(readAsciiLong(8, 16));
    312            ret.setTime(readAsciiLong(8, 16));
    313            ret.setSize(readAsciiLong(8, 16));
    314            ret.setDeviceMaj(readAsciiLong(8, 16));
    315            ret.setDeviceMin(readAsciiLong(8, 16));
    316            ret.setRemoteDeviceMaj(readAsciiLong(8, 16));
    317            ret.setRemoteDeviceMin(readAsciiLong(8, 16));
    318            long namesize = readAsciiLong(8, 16);
    319            ret.setChksum(readAsciiLong(8, 16));
    320            String name = readCString((int) namesize);
    321            ret.setName(name);
    322            if (mode == 0 && !name.equals(CPIO_TRAILER)){
    323                throw new IOException("Mode 0 only allowed in the trailer. Found entry name: "+name + " Occured at byte: " + getBytesRead());
    324            }
    325            skip(ret.getHeaderPadCount());
    326
    327            return ret;
    328        }
    329
    330        private CpioArchiveEntry readOldAsciiEntry() throws IOException {
    331            CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_ASCII);
    332
    333            ret.setDevice(readAsciiLong(6, 8));
    334            ret.setInode(readAsciiLong(6, 8));
    335            final long mode = readAsciiLong(6, 8);
    336            if (mode != 0) {
    337                ret.setMode(mode);
    338            }
    339            ret.setUID(readAsciiLong(6, 8));
    340            ret.setGID(readAsciiLong(6, 8));
    341            ret.setNumberOfLinks(readAsciiLong(6, 8));
    342            ret.setRemoteDevice(readAsciiLong(6, 8));
    343            ret.setTime(readAsciiLong(11, 8));
    344            long namesize = readAsciiLong(6, 8);
    345            ret.setSize(readAsciiLong(11, 8));
    346            final String name = readCString((int) namesize);
    347            ret.setName(name);
    348            if (mode == 0 && !name.equals(CPIO_TRAILER)){
    349                throw new IOException("Mode 0 only allowed in the trailer. Found entry: "+ name + " Occured at byte: " + getBytesRead());
    350            }
    351
    352            return ret;
    353        }
    354
    355        private CpioArchiveEntry readOldBinaryEntry(final boolean swapHalfWord)
    356                throws IOException {
    357            CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_BINARY);
    358
    359            ret.setDevice(readBinaryLong(2, swapHalfWord));
    360            ret.setInode(readBinaryLong(2, swapHalfWord));
    361            final long mode = readBinaryLong(2, swapHalfWord);
    362            if (mode != 0){
    363                ret.setMode(mode);
    364            }
    365            ret.setUID(readBinaryLong(2, swapHalfWord));
    366            ret.setGID(readBinaryLong(2, swapHalfWord));
    367            ret.setNumberOfLinks(readBinaryLong(2, swapHalfWord));
    368            ret.setRemoteDevice(readBinaryLong(2, swapHalfWord));
    369            ret.setTime(readBinaryLong(4, swapHalfWord));
    370            long namesize = readBinaryLong(2, swapHalfWord);
    371            ret.setSize(readBinaryLong(4, swapHalfWord));
    372            final String name = readCString((int) namesize);
    373            ret.setName(name);
    374            if (mode == 0 && !name.equals(CPIO_TRAILER)){
    375                throw new IOException("Mode 0 only allowed in the trailer. Found entry: "+name + "Occured at byte: " + getBytesRead());
    376            }
    377            skip(ret.getHeaderPadCount());
    378
    379            return ret;
    380        }
    381
    382        private String readCString(final int length) throws IOException {
    383            byte tmpBuffer[] = new byte[length];
    384            readFully(tmpBuffer, 0, tmpBuffer.length);
    385            return new String(tmpBuffer, 0, tmpBuffer.length - 1);
    386        }
    387
    388        /**
    389         * Skips specified number of bytes in the current CPIO entry.
    390         *
    391         * @param n
    392         *            the number of bytes to skip
    393         * @return the actual number of bytes skipped
    394         * @throws IOException
    395         *             if an I/O error has occurred
    396         * @throws IllegalArgumentException
    397         *             if n < 0
    398         */
    399        public long skip(final long n) throws IOException {
    400            if (n < 0) {
    401                throw new IllegalArgumentException("negative skip length");
    402            }
    403            ensureOpen();
    404            int max = (int) Math.min(n, Integer.MAX_VALUE);
    405            int total = 0;
    406
    407            while (total < max) {
    408                int len = max - total;
    409                if (len > this.tmpbuf.length) {
    410                    len = this.tmpbuf.length;
    411                }
    412                len = read(this.tmpbuf, 0, len);
    413                if (len == -1) {
    414                    this.entryEOF = true;
    415                    break;
    416                }
    417                total += len;
    418            }
    419            return total;
    420        }
    421
    422        /** {@inheritDoc} */
    423        public ArchiveEntry getNextEntry() throws IOException {
    424            return getNextCPIOEntry();
    425        }
    426
    427        /**
    428         * Checks if the signature matches one of the following magic values:
    429         *
    430         * Strings:
    431         *
    432         * "070701" - MAGIC_NEW
    433         * "070702" - MAGIC_NEW_CRC
    434         * "070707" - MAGIC_OLD_ASCII
    435         *
    436         * Octal Binary value:
    437         *
    438         * 070707 - MAGIC_OLD_BINARY (held as a short) = 0x71C7 or 0xC771
    439         */
    440        public static boolean matches(byte[] signature, int length) {
    441            if (length < 6) {
    442                return false;
    443            }
    444
    445            // Check binary values
    446            if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7) {
    447                return true;
    448            }
    449            if (signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) {
    450                return true;
    451            }
    452
    453            // Check Ascii (String) values
    454            // 3037 3037 30nn
    455            if (signature[0] != 0x30) {
    456                return false;
    457            }
    458            if (signature[1] != 0x37) {
    459                return false;
    460            }
    461            if (signature[2] != 0x30) {
    462                return false;
    463            }
    464            if (signature[3] != 0x37) {
    465                return false;
    466            }
    467            if (signature[4] != 0x30) {
    468                return false;
    469            }
    470            // Check last byte
    471            if (signature[5] == 0x31) {
    472                return true;
    473            }
    474            if (signature[5] == 0x32) {
    475                return true;
    476            }
    477            if (signature[5] == 0x37) {
    478                return true;
    479            }
    480
    481            return false;
    482        }
    483    }