1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.commons.compress.archivers.cpio;
20
21 import java.io.EOFException;
22 import java.io.IOException;
23 import java.io.InputStream;
24
25 import org.apache.commons.compress.archivers.ArchiveInputStream;
26 import org.apache.commons.compress.archivers.zip.ZipEncoding;
27 import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
28 import org.apache.commons.compress.utils.ArchiveUtils;
29 import org.apache.commons.compress.utils.IOUtils;
30 import org.apache.commons.compress.utils.ParsingUtils;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 public class CpioArchiveInputStream extends ArchiveInputStream<CpioArchiveEntry> implements CpioConstants {
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 public static boolean matches(final byte[] signature, final int length) {
82 if (length < 6) {
83 return false;
84 }
85
86
87 if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7) {
88 return true;
89 }
90 if (signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) {
91 return true;
92 }
93
94
95
96 if (signature[0] != 0x30) {
97 return false;
98 }
99 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
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
138 private final byte[] twoBytesBuf = new byte[2];
139
140
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
149
150 private final ZipEncoding zipEncoding;
151
152
153
154
155
156
157 public CpioArchiveInputStream(final InputStream in) {
158 this(in, BLOCK_SIZE, CpioUtil.DEFAULT_CHARSET_NAME);
159 }
160
161
162
163
164
165
166
167
168 public CpioArchiveInputStream(final InputStream in, final int blockSize) {
169 this(in, blockSize, CpioUtil.DEFAULT_CHARSET_NAME);
170 }
171
172
173
174
175
176
177
178
179
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
193
194
195
196
197
198 public CpioArchiveInputStream(final InputStream in, final String encoding) {
199 this(in, BLOCK_SIZE, encoding);
200 }
201
202
203
204
205
206
207
208
209
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
222
223
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
235
236
237
238 private void closeEntry() throws IOException {
239
240
241 while (skip((long) Integer.MAX_VALUE) == Integer.MAX_VALUE) {
242
243 }
244 }
245
246
247
248
249
250
251 private void ensureOpen() throws IOException {
252 if (this.closed) {
253 throw new IOException("Stream closed");
254 }
255 }
256
257
258
259
260
261
262
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
313
314
315
316
317
318
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;
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
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) {
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
503 if (bytes > 0) {
504 readFully(fourBytesBuf, 0, bytes);
505 }
506 }
507
508
509
510
511
512
513
514
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
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 }