Home | History | Annotate | Download | only in cpio
      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.cpio;
     20 
     21 import java.io.EOFException;
     22 import java.io.IOException;
     23 import java.io.InputStream;
     24 
     25 import org.apache.commons.compress.archivers.ArchiveEntry;
     26 import org.apache.commons.compress.archivers.ArchiveInputStream;
     27 import org.apache.commons.compress.archivers.zip.ZipEncoding;
     28 import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
     29 import org.apache.commons.compress.utils.ArchiveUtils;
     30 import org.apache.commons.compress.utils.CharsetNames;
     31 import org.apache.commons.compress.utils.IOUtils;
     32 
     33 /**
     34  * CpioArchiveInputStream is a stream for reading cpio streams. All formats of
     35  * cpio are supported (old ascii, old binary, new portable format and the new
     36  * portable format with crc).
     37  *
     38  * <p>
     39  * The stream can be read by extracting a cpio entry (containing all
     40  * informations about a entry) and afterwards reading from the stream the file
     41  * specified by the entry.
     42  * </p>
     43  * <pre>
     44  * CpioArchiveInputStream cpioIn = new CpioArchiveInputStream(
     45  *         Files.newInputStream(Paths.get(&quot;test.cpio&quot;)));
     46  * CpioArchiveEntry cpioEntry;
     47  *
     48  * while ((cpioEntry = cpioIn.getNextEntry()) != null) {
     49  *     System.out.println(cpioEntry.getName());
     50  *     int tmp;
     51  *     StringBuilder buf = new StringBuilder();
     52  *     while ((tmp = cpIn.read()) != -1) {
     53  *         buf.append((char) tmp);
     54  *     }
     55  *     System.out.println(buf.toString());
     56  * }
     57  * cpioIn.close();
     58  * </pre>
     59  * <p>
     60  * Note: This implementation should be compatible to cpio 2.5
     61  *
     62  * <p>This class uses mutable fields and is not considered to be threadsafe.
     63  *
     64  * <p>Based on code from the jRPM project (jrpm.sourceforge.net)
     65  */
     66 
     67 public class CpioArchiveInputStream extends ArchiveInputStream implements
     68         CpioConstants {
     69 
     70     private boolean closed = false;
     71 
     72     private CpioArchiveEntry entry;
     73 
     74     private long entryBytesRead = 0;
     75 
     76     private boolean entryEOF = false;
     77 
     78     private final byte tmpbuf[] = new byte[4096];
     79 
     80     private long crc = 0;
     81 
     82     private final InputStream in;
     83 
     84     // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
     85     private final byte[] twoBytesBuf = new byte[2];
     86     private final byte[] fourBytesBuf = new byte[4];
     87     private final byte[] sixBytesBuf = new byte[6];
     88 
     89     private final int blockSize;
     90 
     91     /**
     92      * The encoding to use for filenames and labels.
     93      */
     94     private final ZipEncoding zipEncoding;
     95 
     96     // the provided encoding (for unit tests)
     97     final String encoding;
     98 
     99     /**
    100      * Construct the cpio input stream with a blocksize of {@link
    101      * CpioConstants#BLOCK_SIZE BLOCK_SIZE} and expecting ASCII file
    102      * names.
    103      *
    104      * @param in
    105      *            The cpio stream
    106      */
    107     public CpioArchiveInputStream(final InputStream in) {
    108         this(in, BLOCK_SIZE, CharsetNames.US_ASCII);
    109     }
    110 
    111     /**
    112      * Construct the cpio input stream with a blocksize of {@link
    113      * CpioConstants#BLOCK_SIZE BLOCK_SIZE}.
    114      *
    115      * @param in
    116      *            The cpio stream
    117      * @param encoding
    118      *            The encoding of file names to expect - use null for
    119      *            the platform's default.
    120      * @since 1.6
    121      */
    122     public CpioArchiveInputStream(final InputStream in, final String encoding) {
    123         this(in, BLOCK_SIZE, encoding);
    124     }
    125 
    126     /**
    127      * Construct the cpio input stream with a blocksize of {@link
    128      * CpioConstants#BLOCK_SIZE BLOCK_SIZE} expecting ASCII file
    129      * names.
    130      *
    131      * @param in
    132      *            The cpio stream
    133      * @param blockSize
    134      *            The block size of the archive.
    135      * @since 1.5
    136      */
    137     public CpioArchiveInputStream(final InputStream in, final int blockSize) {
    138         this(in, blockSize, CharsetNames.US_ASCII);
    139     }
    140 
    141     /**
    142      * Construct the cpio input stream with a blocksize of {@link CpioConstants#BLOCK_SIZE BLOCK_SIZE}.
    143      *
    144      * @param in
    145      *            The cpio stream
    146      * @param blockSize
    147      *            The block size of the archive.
    148      * @param encoding
    149      *            The encoding of file names to expect - use null for
    150      *            the platform's default.
    151      * @since 1.6
    152      */
    153     public CpioArchiveInputStream(final InputStream in, final int blockSize, final String encoding) {
    154         this.in = in;
    155         this.blockSize = blockSize;
    156         this.encoding = encoding;
    157         this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
    158     }
    159 
    160     /**
    161      * Returns 0 after EOF has reached for the current entry data, otherwise
    162      * always return 1.
    163      * <p>
    164      * Programs should not count on this method to return the actual number of
    165      * bytes that could be read without blocking.
    166      *
    167      * @return 1 before EOF and 0 after EOF has reached for current entry.
    168      * @throws IOException
    169      *             if an I/O error has occurred or if a CPIO file error has
    170      *             occurred
    171      */
    172     @Override
    173     public int available() throws IOException {
    174         ensureOpen();
    175         if (this.entryEOF) {
    176             return 0;
    177         }
    178         return 1;
    179     }
    180 
    181     /**
    182      * Closes the CPIO input stream.
    183      *
    184      * @throws IOException
    185      *             if an I/O error has occurred
    186      */
    187     @Override
    188     public void close() throws IOException {
    189         if (!this.closed) {
    190             in.close();
    191             this.closed = true;
    192         }
    193     }
    194 
    195     /**
    196      * Closes the current CPIO entry and positions the stream for reading the
    197      * next entry.
    198      *
    199      * @throws IOException
    200      *             if an I/O error has occurred or if a CPIO file error has
    201      *             occurred
    202      */
    203     private void closeEntry() throws IOException {
    204         // the skip implementation of this class will not skip more
    205         // than Integer.MAX_VALUE bytes
    206         while (skip((long) Integer.MAX_VALUE) == Integer.MAX_VALUE) { // NOPMD
    207             // do nothing
    208         }
    209     }
    210 
    211     /**
    212      * Check to make sure that this stream has not been closed
    213      *
    214      * @throws IOException
    215      *             if the stream is already closed
    216      */
    217     private void ensureOpen() throws IOException {
    218         if (this.closed) {
    219             throw new IOException("Stream closed");
    220         }
    221     }
    222 
    223     /**
    224      * Reads the next CPIO file entry and positions stream at the beginning of
    225      * the entry data.
    226      *
    227      * @return the CpioArchiveEntry just read
    228      * @throws IOException
    229      *             if an I/O error has occurred or if a CPIO file error has
    230      *             occurred
    231      */
    232     public CpioArchiveEntry getNextCPIOEntry() throws IOException {
    233         ensureOpen();
    234         if (this.entry != null) {
    235             closeEntry();
    236         }
    237         readFully(twoBytesBuf, 0, twoBytesBuf.length);
    238         if (CpioUtil.byteArray2long(twoBytesBuf, false) == MAGIC_OLD_BINARY) {
    239             this.entry = readOldBinaryEntry(false);
    240         } else if (CpioUtil.byteArray2long(twoBytesBuf, true)
    241                    == MAGIC_OLD_BINARY) {
    242             this.entry = readOldBinaryEntry(true);
    243         } else {
    244             System.arraycopy(twoBytesBuf, 0, sixBytesBuf, 0,
    245                              twoBytesBuf.length);
    246             readFully(sixBytesBuf, twoBytesBuf.length,
    247                       fourBytesBuf.length);
    248             final String magicString = ArchiveUtils.toAsciiString(sixBytesBuf);
    249             switch (magicString) {
    250                 case MAGIC_NEW:
    251                     this.entry = readNewEntry(false);
    252                     break;
    253                 case MAGIC_NEW_CRC:
    254                     this.entry = readNewEntry(true);
    255                     break;
    256                 case MAGIC_OLD_ASCII:
    257                     this.entry = readOldAsciiEntry();
    258                     break;
    259                 default:
    260                     throw new IOException("Unknown magic [" + magicString + "]. Occured at byte: " + getBytesRead());
    261             }
    262         }
    263 
    264         this.entryBytesRead = 0;
    265         this.entryEOF = false;
    266         this.crc = 0;
    267 
    268         if (this.entry.getName().equals(CPIO_TRAILER)) {
    269             this.entryEOF = true;
    270             skipRemainderOfLastBlock();
    271             return null;
    272         }
    273         return this.entry;
    274     }
    275 
    276     private void skip(final int bytes) throws IOException{
    277         // bytes cannot be more than 3 bytes
    278         if (bytes > 0) {
    279             readFully(fourBytesBuf, 0, bytes);
    280         }
    281     }
    282 
    283     /**
    284      * Reads from the current CPIO entry into an array of bytes. Blocks until
    285      * some input is available.
    286      *
    287      * @param b
    288      *            the buffer into which the data is read
    289      * @param off
    290      *            the start offset of the data
    291      * @param len
    292      *            the maximum number of bytes read
    293      * @return the actual number of bytes read, or -1 if the end of the entry is
    294      *         reached
    295      * @throws IOException
    296      *             if an I/O error has occurred or if a CPIO file error has
    297      *             occurred
    298      */
    299     @Override
    300     public int read(final byte[] b, final int off, final int len)
    301             throws IOException {
    302         ensureOpen();
    303         if (off < 0 || len < 0 || off > b.length - len) {
    304             throw new IndexOutOfBoundsException();
    305         } else if (len == 0) {
    306             return 0;
    307         }
    308 
    309         if (this.entry == null || this.entryEOF) {
    310             return -1;
    311         }
    312         if (this.entryBytesRead == this.entry.getSize()) {
    313             skip(entry.getDataPadCount());
    314             this.entryEOF = true;
    315             if (this.entry.getFormat() == FORMAT_NEW_CRC
    316                 && this.crc != this.entry.getChksum()) {
    317                 throw new IOException("CRC Error. Occured at byte: "
    318                                       + getBytesRead());
    319             }
    320             return -1; // EOF for this entry
    321         }
    322         final int tmplength = (int) Math.min(len, this.entry.getSize()
    323                 - this.entryBytesRead);
    324         if (tmplength < 0) {
    325             return -1;
    326         }
    327 
    328         final int tmpread = readFully(b, off, tmplength);
    329         if (this.entry.getFormat() == FORMAT_NEW_CRC) {
    330             for (int pos = 0; pos < tmpread; pos++) {
    331                 this.crc += b[pos] & 0xFF;
    332                 this.crc &= 0xFFFFFFFFL;
    333             }
    334         }
    335         if (tmpread > 0) {
    336             this.entryBytesRead += tmpread;
    337         }
    338 
    339         return tmpread;
    340     }
    341 
    342     private final int readFully(final byte[] b, final int off, final int len)
    343             throws IOException {
    344         final int count = IOUtils.readFully(in, b, off, len);
    345         count(count);
    346         if (count < len) {
    347             throw new EOFException();
    348         }
    349         return count;
    350     }
    351 
    352     private long readBinaryLong(final int length, final boolean swapHalfWord)
    353             throws IOException {
    354         final byte tmp[] = new byte[length];
    355         readFully(tmp, 0, tmp.length);
    356         return CpioUtil.byteArray2long(tmp, swapHalfWord);
    357     }
    358 
    359     private long readAsciiLong(final int length, final int radix)
    360             throws IOException {
    361         final byte tmpBuffer[] = new byte[length];
    362         readFully(tmpBuffer, 0, tmpBuffer.length);
    363         return Long.parseLong(ArchiveUtils.toAsciiString(tmpBuffer), radix);
    364     }
    365 
    366     private CpioArchiveEntry readNewEntry(final boolean hasCrc)
    367             throws IOException {
    368         CpioArchiveEntry ret;
    369         if (hasCrc) {
    370             ret = new CpioArchiveEntry(FORMAT_NEW_CRC);
    371         } else {
    372             ret = new CpioArchiveEntry(FORMAT_NEW);
    373         }
    374 
    375         ret.setInode(readAsciiLong(8, 16));
    376         final long mode = readAsciiLong(8, 16);
    377         if (CpioUtil.fileType(mode) != 0){ // mode is initialised to 0
    378             ret.setMode(mode);
    379         }
    380         ret.setUID(readAsciiLong(8, 16));
    381         ret.setGID(readAsciiLong(8, 16));
    382         ret.setNumberOfLinks(readAsciiLong(8, 16));
    383         ret.setTime(readAsciiLong(8, 16));
    384         ret.setSize(readAsciiLong(8, 16));
    385         ret.setDeviceMaj(readAsciiLong(8, 16));
    386         ret.setDeviceMin(readAsciiLong(8, 16));
    387         ret.setRemoteDeviceMaj(readAsciiLong(8, 16));
    388         ret.setRemoteDeviceMin(readAsciiLong(8, 16));
    389         final long namesize = readAsciiLong(8, 16);
    390         ret.setChksum(readAsciiLong(8, 16));
    391         final String name = readCString((int) namesize);
    392         ret.setName(name);
    393         if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)){
    394             throw new IOException("Mode 0 only allowed in the trailer. Found entry name: "
    395                                   + ArchiveUtils.sanitize(name)
    396                                   + " Occured at byte: " + getBytesRead());
    397         }
    398         skip(ret.getHeaderPadCount(namesize - 1));
    399 
    400         return ret;
    401     }
    402 
    403     private CpioArchiveEntry readOldAsciiEntry() throws IOException {
    404         final CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_ASCII);
    405 
    406         ret.setDevice(readAsciiLong(6, 8));
    407         ret.setInode(readAsciiLong(6, 8));
    408         final long mode = readAsciiLong(6, 8);
    409         if (CpioUtil.fileType(mode) != 0) {
    410             ret.setMode(mode);
    411         }
    412         ret.setUID(readAsciiLong(6, 8));
    413         ret.setGID(readAsciiLong(6, 8));
    414         ret.setNumberOfLinks(readAsciiLong(6, 8));
    415         ret.setRemoteDevice(readAsciiLong(6, 8));
    416         ret.setTime(readAsciiLong(11, 8));
    417         final long namesize = readAsciiLong(6, 8);
    418         ret.setSize(readAsciiLong(11, 8));
    419         final String name = readCString((int) namesize);
    420         ret.setName(name);
    421         if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)){
    422             throw new IOException("Mode 0 only allowed in the trailer. Found entry: "
    423                                   + ArchiveUtils.sanitize(name)
    424                                   + " Occured at byte: " + getBytesRead());
    425         }
    426 
    427         return ret;
    428     }
    429 
    430     private CpioArchiveEntry readOldBinaryEntry(final boolean swapHalfWord)
    431             throws IOException {
    432         final CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_BINARY);
    433 
    434         ret.setDevice(readBinaryLong(2, swapHalfWord));
    435         ret.setInode(readBinaryLong(2, swapHalfWord));
    436         final long mode = readBinaryLong(2, swapHalfWord);
    437         if (CpioUtil.fileType(mode) != 0){
    438             ret.setMode(mode);
    439         }
    440         ret.setUID(readBinaryLong(2, swapHalfWord));
    441         ret.setGID(readBinaryLong(2, swapHalfWord));
    442         ret.setNumberOfLinks(readBinaryLong(2, swapHalfWord));
    443         ret.setRemoteDevice(readBinaryLong(2, swapHalfWord));
    444         ret.setTime(readBinaryLong(4, swapHalfWord));
    445         final long namesize = readBinaryLong(2, swapHalfWord);
    446         ret.setSize(readBinaryLong(4, swapHalfWord));
    447         final String name = readCString((int) namesize);
    448         ret.setName(name);
    449         if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)){
    450             throw new IOException("Mode 0 only allowed in the trailer. Found entry: "
    451                                   + ArchiveUtils.sanitize(name)
    452                                   + "Occured at byte: " + getBytesRead());
    453         }
    454         skip(ret.getHeaderPadCount(namesize - 1));
    455 
    456         return ret;
    457     }
    458 
    459     private String readCString(final int length) throws IOException {
    460         // don't include trailing NUL in file name to decode
    461         final byte tmpBuffer[] = new byte[length - 1];
    462         readFully(tmpBuffer, 0, tmpBuffer.length);
    463         this.in.read();
    464         return zipEncoding.decode(tmpBuffer);
    465     }
    466 
    467     /**
    468      * Skips specified number of bytes in the current CPIO entry.
    469      *
    470      * @param n
    471      *            the number of bytes to skip
    472      * @return the actual number of bytes skipped
    473      * @throws IOException
    474      *             if an I/O error has occurred
    475      * @throws IllegalArgumentException
    476      *             if n &lt; 0
    477      */
    478     @Override
    479     public long skip(final long n) throws IOException {
    480         if (n < 0) {
    481             throw new IllegalArgumentException("negative skip length");
    482         }
    483         ensureOpen();
    484         final int max = (int) Math.min(n, Integer.MAX_VALUE);
    485         int total = 0;
    486 
    487         while (total < max) {
    488             int len = max - total;
    489             if (len > this.tmpbuf.length) {
    490                 len = this.tmpbuf.length;
    491             }
    492             len = read(this.tmpbuf, 0, len);
    493             if (len == -1) {
    494                 this.entryEOF = true;
    495                 break;
    496             }
    497             total += len;
    498         }
    499         return total;
    500     }
    501 
    502     @Override
    503     public ArchiveEntry getNextEntry() throws IOException {
    504         return getNextCPIOEntry();
    505     }
    506 
    507     /**
    508      * Skips the padding zeros written after the TRAILER!!! entry.
    509      */
    510     private void skipRemainderOfLastBlock() throws IOException {
    511         final long readFromLastBlock = getBytesRead() % blockSize;
    512         long remainingBytes = readFromLastBlock == 0 ? 0
    513             : blockSize - readFromLastBlock;
    514         while (remainingBytes > 0) {
    515             final long skipped = skip(blockSize - readFromLastBlock);
    516             if (skipped <= 0) {
    517                 break;
    518             }
    519             remainingBytes -= skipped;
    520         }
    521     }
    522 
    523     /**
    524      * Checks if the signature matches one of the following magic values:
    525      *
    526      * Strings:
    527      *
    528      * "070701" - MAGIC_NEW
    529      * "070702" - MAGIC_NEW_CRC
    530      * "070707" - MAGIC_OLD_ASCII
    531      *
    532      * Octal Binary value:
    533      *
    534      * 070707 - MAGIC_OLD_BINARY (held as a short) = 0x71C7 or 0xC771
    535      * @param signature data to match
    536      * @param length length of data
    537      * @return whether the buffer seems to contain CPIO data
    538      */
    539     public static boolean matches(final byte[] signature, final int length) {
    540         if (length < 6) {
    541             return false;
    542         }
    543 
    544         // Check binary values
    545         if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7) {
    546             return true;
    547         }
    548         if (signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) {
    549             return true;
    550         }
    551 
    552         // Check Ascii (String) values
    553         // 3037 3037 30nn
    554         if (signature[0] != 0x30) {
    555             return false;
    556         }
    557         if (signature[1] != 0x37) {
    558             return false;
    559         }
    560         if (signature[2] != 0x30) {
    561             return false;
    562         }
    563         if (signature[3] != 0x37) {
    564             return false;
    565         }
    566         if (signature[4] != 0x30) {
    567             return false;
    568         }
    569         // Check last byte
    570         if (signature[5] == 0x31) {
    571             return true;
    572         }
    573         if (signature[5] == 0x32) {
    574             return true;
    575         }
    576         if (signature[5] == 0x37) {
    577             return true;
    578         }
    579 
    580         return false;
    581     }
    582 }
    583