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