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); // TODO default charset?
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    }