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("test.cpio"))); 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 < 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}