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 if (signature[0] == 0x71 && (signature[1] & 0xFF) == 0xc7 || signature[1] == 0x71 && (signature[0] & 0xFF) == 0xc7) {
87 return true;
88 }
89
90
91 if (signature[0] != 0x30) {
92 return false;
93 }
94 if (signature[1] != 0x37) {
95 return false;
96 }
97 if (signature[2] != 0x30) {
98 return false;
99 }
100 if (signature[3] != 0x37) {
101 return false;
102 }
103 if (signature[4] != 0x30) {
104 return false;
105 }
106
107 if (signature[5] == 0x31) {
108 return true;
109 }
110 if (signature[5] == 0x32) {
111 return true;
112 }
113 if (signature[5] == 0x37) {
114 return true;
115 }
116 return false;
117 }
118
119 private boolean closed;
120
121 private CpioArchiveEntry entry;
122
123 private long entryBytesRead;
124
125 private boolean entryEOF;
126
127 private final byte[] tmpBuf = new byte[4096];
128
129 private long crc;
130
131
132 private final byte[] buffer2 = new byte[2];
133
134
135 private final byte[] buffer4 = new byte[4];
136
137 private final byte[] buffer6 = new byte[6];
138
139 private final int blockSize;
140
141
142
143
144 private final ZipEncoding zipEncoding;
145
146
147
148
149
150
151 public CpioArchiveInputStream(final InputStream in) {
152 this(in, BLOCK_SIZE, CpioUtil.DEFAULT_CHARSET_NAME);
153 }
154
155
156
157
158
159
160
161
162 public CpioArchiveInputStream(final InputStream in, final int blockSize) {
163 this(in, blockSize, CpioUtil.DEFAULT_CHARSET_NAME);
164 }
165
166
167
168
169
170
171
172
173
174
175 public CpioArchiveInputStream(final InputStream in, final int blockSize, final String encoding) {
176 super(in, encoding);
177 this.in = in;
178 if (blockSize <= 0) {
179 throw new IllegalArgumentException("blockSize must be bigger than 0");
180 }
181 this.blockSize = blockSize;
182 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
183 }
184
185
186
187
188
189
190
191
192 public CpioArchiveInputStream(final InputStream in, final String encoding) {
193 this(in, BLOCK_SIZE, encoding);
194 }
195
196
197
198
199
200
201
202
203
204
205 @Override
206 public int available() throws IOException {
207 ensureOpen();
208 if (this.entryEOF) {
209 return 0;
210 }
211 return 1;
212 }
213
214
215
216
217
218
219 @Override
220 public void close() throws IOException {
221 if (!this.closed) {
222 in.close();
223 this.closed = true;
224 }
225 }
226
227
228
229
230
231
232 private void closeEntry() throws IOException {
233
234
235 while (skip((long) Integer.MAX_VALUE) == Integer.MAX_VALUE) {
236
237 }
238 }
239
240
241
242
243
244
245 private void ensureOpen() throws IOException {
246 if (this.closed) {
247 throw new IOException("Stream closed");
248 }
249 }
250
251
252
253
254
255
256
257
258 @Deprecated
259 public CpioArchiveEntry getNextCPIOEntry() throws IOException {
260 ensureOpen();
261 if (this.entry != null) {
262 closeEntry();
263 }
264 readFully(buffer2, 0, buffer2.length);
265 if (CpioUtil.byteArray2long(buffer2, false) == MAGIC_OLD_BINARY) {
266 this.entry = readOldBinaryEntry(false);
267 } else if (CpioUtil.byteArray2long(buffer2, true) == MAGIC_OLD_BINARY) {
268 this.entry = readOldBinaryEntry(true);
269 } else {
270 System.arraycopy(buffer2, 0, buffer6, 0, buffer2.length);
271 readFully(buffer6, buffer2.length, buffer4.length);
272 final String magicString = ArchiveUtils.toAsciiString(buffer6);
273 switch (magicString) {
274 case MAGIC_NEW:
275 this.entry = readNewEntry(false);
276 break;
277 case MAGIC_NEW_CRC:
278 this.entry = readNewEntry(true);
279 break;
280 case MAGIC_OLD_ASCII:
281 this.entry = readOldAsciiEntry();
282 break;
283 default:
284 throw new IOException("Unknown magic [" + magicString + "]. Occurred at byte: " + getBytesRead());
285 }
286 }
287
288 this.entryBytesRead = 0;
289 this.entryEOF = false;
290 this.crc = 0;
291
292 if (this.entry.getName().equals(CPIO_TRAILER)) {
293 this.entryEOF = true;
294 skipRemainderOfLastBlock();
295 return null;
296 }
297 return this.entry;
298 }
299
300 @Override
301 public CpioArchiveEntry getNextEntry() throws IOException {
302 return getNextCPIOEntry();
303 }
304
305
306
307
308
309
310
311
312
313
314 @Override
315 public int read(final byte[] b, final int off, final int len) throws IOException {
316 ensureOpen();
317 if (off < 0 || len < 0 || off > b.length - len) {
318 throw new IndexOutOfBoundsException();
319 }
320 if (len == 0) {
321 return 0;
322 }
323
324 if (this.entry == null || this.entryEOF) {
325 return -1;
326 }
327 if (this.entryBytesRead == this.entry.getSize()) {
328 final int dataPadCount = entry.getDataPadCount();
329 if (skip(dataPadCount) != dataPadCount) {
330 throw new IOException("Data pad count missmatch.");
331 }
332 this.entryEOF = true;
333 if (this.entry.getFormat() == FORMAT_NEW_CRC && this.crc != this.entry.getChksum()) {
334 throw new IOException("CRC Error. Occurred at byte: " + getBytesRead());
335 }
336 return -1;
337 }
338 final int tmplength = (int) Math.min(len, this.entry.getSize() - this.entryBytesRead);
339 if (tmplength < 0) {
340 return -1;
341 }
342
343 final int tmpread = readFully(b, off, tmplength);
344 if (this.entry.getFormat() == FORMAT_NEW_CRC) {
345 for (int pos = 0; pos < tmpread; pos++) {
346 this.crc += b[pos] & 0xFF;
347 this.crc &= 0xFFFFFFFFL;
348 }
349 }
350 if (tmpread > 0) {
351 this.entryBytesRead += tmpread;
352 }
353
354 return tmpread;
355 }
356
357 private long readAsciiLong(final int length, final int radix) throws IOException {
358 final byte[] tmpBuffer = readRange(length);
359 return ParsingUtils.parseLongValue(ArchiveUtils.toAsciiString(tmpBuffer), radix);
360 }
361
362 private long readBinaryLong(final int length, final boolean swapHalfWord) throws IOException {
363 final byte[] tmp = readRange(length);
364 return CpioUtil.byteArray2long(tmp, swapHalfWord);
365 }
366
367 private String readCString(final int length) throws IOException {
368
369 final byte[] tmpBuffer = readRange(length - 1);
370 if (this.in.read() == -1) {
371 throw new EOFException();
372 }
373 return zipEncoding.decode(tmpBuffer);
374 }
375
376 private int readFully(final byte[] b, final int off, final int len) throws IOException {
377 final int count = IOUtils.readFully(in, b, off, len);
378 count(count);
379 if (count < len) {
380 throw new EOFException();
381 }
382 return count;
383 }
384
385 private CpioArchiveEntry readNewEntry(final boolean hasCrc) throws IOException {
386 final CpioArchiveEntry newEntry;
387 if (hasCrc) {
388 newEntry = new CpioArchiveEntry(FORMAT_NEW_CRC);
389 } else {
390 newEntry = new CpioArchiveEntry(FORMAT_NEW);
391 }
392 newEntry.setInode(readAsciiLong(8, 16));
393 final long mode = readAsciiLong(8, 16);
394 if (CpioUtil.fileType(mode) != 0) {
395 newEntry.setMode(mode);
396 }
397 newEntry.setUID(readAsciiLong(8, 16));
398 newEntry.setGID(readAsciiLong(8, 16));
399 newEntry.setNumberOfLinks(readAsciiLong(8, 16));
400 newEntry.setTime(readAsciiLong(8, 16));
401 newEntry.setSize(readAsciiLong(8, 16));
402 if (newEntry.getSize() < 0) {
403 throw new IOException("Found illegal entry with negative length");
404 }
405 newEntry.setDeviceMaj(readAsciiLong(8, 16));
406 newEntry.setDeviceMin(readAsciiLong(8, 16));
407 newEntry.setRemoteDeviceMaj(readAsciiLong(8, 16));
408 newEntry.setRemoteDeviceMin(readAsciiLong(8, 16));
409 final long namesize = readAsciiLong(8, 16);
410 if (namesize < 0) {
411 throw new IOException("Found illegal entry with negative name length");
412 }
413 newEntry.setChksum(readAsciiLong(8, 16));
414 final String name = readCString((int) namesize);
415 newEntry.setName(name);
416 if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)) {
417 throw new IOException(
418 "Mode 0 only allowed in the trailer. Found entry name: " + ArchiveUtils.sanitize(name) + " Occurred at byte: " + getBytesRead());
419 }
420 final int headerPadCount = newEntry.getHeaderPadCount(namesize - 1);
421 if (skip(headerPadCount) != headerPadCount) {
422 throw new IOException("Header pad count mismatch.");
423 }
424 return newEntry;
425 }
426
427 private CpioArchiveEntry readOldAsciiEntry() throws IOException {
428 final CpioArchiveEntry ret = new CpioArchiveEntry(FORMAT_OLD_ASCII);
429
430 ret.setDevice(readAsciiLong(6, 8));
431 ret.setInode(readAsciiLong(6, 8));
432 final long mode = readAsciiLong(6, 8);
433 if (CpioUtil.fileType(mode) != 0) {
434 ret.setMode(mode);
435 }
436 ret.setUID(readAsciiLong(6, 8));
437 ret.setGID(readAsciiLong(6, 8));
438 ret.setNumberOfLinks(readAsciiLong(6, 8));
439 ret.setRemoteDevice(readAsciiLong(6, 8));
440 ret.setTime(readAsciiLong(11, 8));
441 final long namesize = readAsciiLong(6, 8);
442 if (namesize < 0) {
443 throw new IOException("Found illegal entry with negative name length");
444 }
445 ret.setSize(readAsciiLong(11, 8));
446 if (ret.getSize() < 0) {
447 throw new IOException("Found illegal entry with negative length");
448 }
449 final String name = readCString((int) namesize);
450 ret.setName(name);
451 if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)) {
452 throw new IOException("Mode 0 only allowed in the trailer. Found entry: " + ArchiveUtils.sanitize(name) + " Occurred at byte: " + getBytesRead());
453 }
454
455 return ret;
456 }
457
458 private CpioArchiveEntry readOldBinaryEntry(final boolean swapHalfWord) throws IOException {
459 final CpioArchiveEntry oldEntry = new CpioArchiveEntry(FORMAT_OLD_BINARY);
460 oldEntry.setDevice(readBinaryLong(2, swapHalfWord));
461 oldEntry.setInode(readBinaryLong(2, swapHalfWord));
462 final long mode = readBinaryLong(2, swapHalfWord);
463 if (CpioUtil.fileType(mode) != 0) {
464 oldEntry.setMode(mode);
465 }
466 oldEntry.setUID(readBinaryLong(2, swapHalfWord));
467 oldEntry.setGID(readBinaryLong(2, swapHalfWord));
468 oldEntry.setNumberOfLinks(readBinaryLong(2, swapHalfWord));
469 oldEntry.setRemoteDevice(readBinaryLong(2, swapHalfWord));
470 oldEntry.setTime(readBinaryLong(4, swapHalfWord));
471 final long namesize = readBinaryLong(2, swapHalfWord);
472 if (namesize < 0) {
473 throw new IOException("Found illegal entry with negative name length");
474 }
475 oldEntry.setSize(readBinaryLong(4, swapHalfWord));
476 if (oldEntry.getSize() < 0) {
477 throw new IOException("Found illegal entry with negative length");
478 }
479 final String name = readCString((int) namesize);
480 oldEntry.setName(name);
481 if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)) {
482 throw new IOException("Mode 0 only allowed in the trailer. Found entry: " + ArchiveUtils.sanitize(name) + "Occurred at byte: " + getBytesRead());
483 }
484 final int headerPadCount = oldEntry.getHeaderPadCount(namesize - 1);
485 if (skip(headerPadCount) != headerPadCount) {
486 throw new IOException("Header pad count mismatch.");
487 }
488 return oldEntry;
489 }
490
491 private byte[] readRange(final int len) throws IOException {
492 final byte[] b = IOUtils.readRange(in, len);
493 count(b.length);
494 if (b.length < len) {
495 throw new EOFException();
496 }
497 return b;
498 }
499
500 private int skip(final int length) throws IOException {
501
502 return length > 0 ? readFully(buffer4, 0, length) : 0;
503 }
504
505
506
507
508
509
510
511
512
513 @Override
514 public long skip(final long n) throws IOException {
515 if (n < 0) {
516 throw new IllegalArgumentException("Negative skip length");
517 }
518 ensureOpen();
519 final int max = (int) Math.min(n, Integer.MAX_VALUE);
520 int total = 0;
521
522 while (total < max) {
523 int len = max - total;
524 if (len > this.tmpBuf.length) {
525 len = this.tmpBuf.length;
526 }
527 len = read(this.tmpBuf, 0, len);
528 if (len == -1) {
529 this.entryEOF = true;
530 break;
531 }
532 total += len;
533 }
534 return total;
535 }
536
537
538
539
540 private void skipRemainderOfLastBlock() throws IOException {
541 final long readFromLastBlock = getBytesRead() % blockSize;
542 long remainingBytes = readFromLastBlock == 0 ? 0 : blockSize - readFromLastBlock;
543 while (remainingBytes > 0) {
544 final long skipped = skip(blockSize - readFromLastBlock);
545 if (skipped <= 0) {
546 break;
547 }
548 remainingBytes -= skipped;
549 }
550 }
551 }