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