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