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: " + getCount());
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: " + getCount());
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: " + getCount());
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: " + getCount());
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: " + getCount());
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        public ArchiveEntry getNextEntry() throws IOException {
423            return getNextCPIOEntry();
424        }
425    
426        /**
427         * Checks if the signature matches one of the following magic values:
428         * 
429         * Strings:
430         *  
431         * "070701" - MAGIC_NEW
432         * "070702" - MAGIC_NEW_CRC
433         * "070707" - MAGIC_OLD_ASCII
434         * 
435         * Octal Binary value:
436         * 
437         * 070707 - MAGIC_OLD_BINARY (held as a short) = 0x71C7 or 0xC771
438         */
439        public static boolean matches(byte[] signature, int length) {
440            if (length < 6) {
441                return false;
442            }
443            
444            // Check binary values
445            if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7) {
446                return true;
447            }
448            if (signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) {
449                return true;
450            }
451    
452            // Check Ascii (String) values
453            // 3037 3037 30nn
454            if (signature[0] != 0x30) {
455                return false;
456            }
457            if (signature[1] != 0x37) {
458                return false;
459            }
460            if (signature[2] != 0x30) {
461                return false;
462            }
463            if (signature[3] != 0x37) {
464                return false;
465            }
466            if (signature[4] != 0x30) {
467                return false;
468            }
469            // Check last byte
470            if (signature[5] == 0x31) {
471                return true;
472            }
473            if (signature[5] == 0x32) {
474                return true;
475            }
476            if (signature[5] == 0x37) {
477                return true;
478            }
479    
480            return false;
481        }
482    }