OSDN Git Service

Add new source files
[armadillo/armadillo1.git] / src / jp / sfjp / armadillo / archive / lzh / DumpLzhHeader.java
1 package jp.sfjp.armadillo.archive.lzh;
2
3 import java.io.*;
4 import java.nio.*;
5 import java.nio.channels.*;
6 import java.util.*;
7 import jp.sfjp.armadillo.archive.*;
8 import jp.sfjp.armadillo.io.*;
9 import jp.sfjp.armadillo.time.*;
10
11 /**
12  * Dump LZH archive header.
13  */
14 public final class DumpLzhHeader extends DumpArchiveHeader {
15
16     private static final TimeT TIME_T = new TimeT();
17     private static final FTime FTIME = new FTime();
18     private static final FileTime FILETIME = new FileTime();
19     private static final int siglen = 5;
20     private static final int siglen_m1 = siglen - 1;
21
22     private static final int USHORT_MASK = 0xFFFF;
23     private static final int UBYTE_MASK = 0xFF;
24
25     private static final int BUFFER_SIZE = 1024;
26     private static final int LEVEL_OFFSET = 20;
27     private static final byte PATH_DELIMITER = (byte)0xFF;
28     private static final String ERROR_PREFIX = "invalid header: ";
29
30     private final ByteBuffer buffer;
31
32     public DumpLzhHeader() {
33         this.buffer = ByteBuffer.allocate(BUFFER_SIZE).order(ByteOrder.LITTLE_ENDIAN);
34     }
35
36     @Override
37     public void dump(InputStream is, PrintWriter out) throws IOException {
38         final int bufferSize = 65536;
39         byte[] bytes = new byte[bufferSize];
40         long p = 0;
41         RewindableInputStream pis = new RewindableInputStream(is, bufferSize);
42         while (true) {
43             Arrays.fill(bytes, (byte)0);
44             final int readSize = pis.read(bytes);
45             if (readSize <= 0)
46                 break;
47             final int offset = findHeader(bytes);
48             if (offset < 0) {
49                 if (readSize < siglen)
50                     break;
51                 pis.rewind(siglen_m1);
52                 p += readSize - siglen_m1;
53                 continue;
54             }
55             final int head = offset - 2;
56             if (offset == 0 || offset < bufferSize - siglen)
57                 pis.rewind(readSize - head);
58             else
59                 throw new AssertionError("else");
60             p += head;
61             printOffset(out, p);
62             try {
63                 p += dumpHeader(pis, out);
64             }
65             catch (Exception ex) {
66                 warn(out, "unexpected error: %s", ex);
67                 p += 7;
68             }
69         }
70         printEnd(out, "LZH", p);
71     }
72
73     static int findHeader(byte[] bytes) {
74         // pattern = "-l**-"
75         for (int i = 0; i < bytes.length - siglen_m1; i++)
76             if (bytes[i] == '-' && bytes[i + 1] == 'l' && bytes[i + 4] == '-')
77                 return i;
78         return -1;
79     }
80
81     public long dumpHeader(InputStream is, PrintWriter out) throws IOException {
82         buffer.clear();
83         buffer.limit(LEVEL_OFFSET + 1);
84         Channels.newChannel(is).read(buffer);
85         int headerLength0 = buffer.position();
86         if (headerLength0 == 0
87             || (headerLength0 == 1 && buffer.get(0) == 0x00)
88             || (headerLength0 <= LEVEL_OFFSET + 1 && buffer.getShort(0) == 0x00))
89             return -1;
90         if (headerLength0 != LEVEL_OFFSET + 1)
91             return -1; // warn: next header length
92         final int level = buffer.get(LEVEL_OFFSET);
93         buffer.rewind();
94         int headerLength;
95         switch (level) {
96             case 0:
97             case 1:
98                 headerLength = (buffer.get() & UBYTE_MASK) + 2;
99                 break;
100             case 2:
101                 headerLength = buffer.getShort() & USHORT_MASK;
102                 break;
103             default:
104                 throw new LzhException("unsupported header level (=" + level + ")");
105         }
106         if (headerLength == 0)
107             return -1;
108         assert headerLength >= 0 && headerLength < BUFFER_SIZE : "header length = " + headerLength;
109         buffer.limit(headerLength);
110         buffer.position(LEVEL_OFFSET + 1);
111         Channels.newChannel(is).read(buffer);
112         if (buffer.position() != headerLength)
113             throw new LzhException(ERROR_PREFIX + "header length = " + headerLength);
114         final long readLength;
115         switch (level) {
116             case 0:
117                 readLength = dumpLevel0(is, out);
118                 break;
119             case 1:
120                 readLength = dumpLevel1(is, out);
121                 break;
122             case 2:
123                 readLength = dumpLevel2(is, out);
124                 break;
125             default:
126                 throw new IllegalStateException("unexpected state");
127         }
128         return readLength;
129     }
130
131     private long dumpLevel0(InputStream is, PrintWriter out) throws IOException {
132         long readLength = 0;
133         // Header Level 0
134         final byte headerLength; // length of this header
135         final byte checksum; // checksum of header (SUM)
136         final byte[] method; // compression method
137         final int skipSize; // skip size
138         final int size; // uncompressed size
139         final short mtime; // last modified time (MS-DOS time)
140         final short mdate; // last modified date (MS-DOS time)
141         final byte attribute; // file attribute
142         final byte level; // header level
143         final byte nameLength; // length of path name
144         final byte[] name; // path name
145         final short crc; // checksum of file (CRC16)
146         final short extend; // -
147         // ---
148         buffer.position(0);
149         headerLength = buffer.get();
150         checksum = buffer.get();
151         method = getBytes(5);
152         skipSize = buffer.getInt();
153         size = buffer.getInt();
154         mtime = buffer.getShort();
155         mdate = buffer.getShort();
156         attribute = buffer.get();
157         level = buffer.get();
158         nameLength = buffer.get();
159         name = getBytes(nameLength);
160         crc = buffer.getShort();
161         extend = buffer.getShort();
162         printHeaderName(out, "Level 0 Header");
163         p(out, "headerLength", headerLength);
164         p(out, "checksum", checksum);
165         p(out, "method", method);
166         p(out, "skipSize", skipSize);
167         p(out, "size", size);
168         p(out, "mtime", mtime);
169         p(out, "mdate", mdate);
170         p(out, "attribute", attribute);
171         p(out, "level", level);
172         p(out, "nameLength", nameLength);
173         p(out, "name", name);
174         p(out, "crc", crc);
175         p(out, "extend", extend);
176         readLength += buffer.position();
177         if (skipSize > 0)
178             readLength += is.skip(skipSize);
179         return readLength;
180     }
181
182     private long dumpLevel1(InputStream is, PrintWriter out) throws IOException {
183         long readLength = 0;
184         // Header Level 1
185         final byte headerLength; // length of this header
186         final byte checksum; // checksum of header (SUM)
187         final byte[] method; // compression method
188         final int skipSize; // skip size
189         final int size; // uncompressed size
190         final short mtime; // last modified time (MS-DOS time)
191         final short mdate; // last modified date (MS-DOS time)
192         final byte reserved; // reserved
193         final byte level; // header level
194         final byte nameLength; // length of path name
195         final byte[] name; // path name
196         final short crc; // checksum of file (CRC16)
197         final byte osIdentifier; // OS identifier which compressed this
198         final short extendHeaderSize; //
199         // ---
200         buffer.position(0);
201         headerLength = buffer.get();
202         checksum = buffer.get();
203         method = getBytes(5);
204         skipSize = buffer.getInt();
205         size = buffer.getInt();
206         mtime = buffer.getShort();
207         mdate = buffer.getShort();
208         reserved = buffer.get();
209         level = buffer.get();
210         nameLength = buffer.get();
211         name = getBytes(nameLength);
212         crc = buffer.getShort();
213         osIdentifier = buffer.get();
214         extendHeaderSize = buffer.getShort();
215         printHeaderName(out, "Level 1 Header");
216         p(out, "headerLength", headerLength);
217         p(out, "checksum", checksum);
218         p(out, "method", method);
219         p(out, "skipSize", skipSize);
220         p(out, "size", size);
221         p(out, "mtime", mtime);
222         p(out, "mdate", mdate);
223         p(out, "reserved", reserved);
224         p(out, "level", level);
225         p(out, "nameLength", nameLength);
226         p(out, "name", name);
227         p(out, "crc", crc & 0xFFFFFFFFL);
228         p(out, "osIdentifier", osIdentifier);
229         p(out, "extendHeaderSize", extendHeaderSize);
230         // p(out, "extendHeader", extendHeader);
231         readLength += buffer.position();
232         if (skipSize > 0)
233             readLength += is.skip(skipSize);
234         return readLength;
235     }
236
237     private long dumpLevel2(InputStream is, PrintWriter out) throws IOException {
238         long readLength = 0;
239         // Header Level 2
240         final short headerLength; // length of this header
241         final byte[] method; // compression method
242         final int compressedSize; // compressed size
243         final int size; // uncompressed size
244         final int mtime; // last modified timestamp (POSIX time)
245         final byte reserved; // reserved
246         final byte level; // header level
247         final short crc; // checksum of file (CRC16)
248         final byte osIdentifier; // OS identifier which compressed this
249         final short firstExHeaderLength; // length of next (first) extend header
250         // ---
251         buffer.position(0);
252         headerLength = buffer.getShort();
253         method = getBytes(5);
254         compressedSize = buffer.getInt();
255         size = buffer.getInt();
256         mtime = buffer.getInt();
257         reserved = buffer.get();
258         level = buffer.get();
259         crc = buffer.getShort();
260         osIdentifier = buffer.get();
261         firstExHeaderLength = buffer.getShort();
262         final String methodName = new String(method);
263         ByteArrayOutputStream x01 = new ByteArrayOutputStream();
264         ByteArrayOutputStream x02 = new ByteArrayOutputStream();
265         List<String> a = new ArrayList<String>();
266         int p = 0;
267         short nextHeaderLength = firstExHeaderLength;
268         while (nextHeaderLength > 0) {
269             final byte identifier = buffer.get();
270             final int length = nextHeaderLength - 3; // datalength = len - 1(id) - 2(nextlen)
271             buffer.mark();
272             a.add(String.format("ex: id=%02X, len=%d", identifier, length));
273             switch (identifier) {
274                 case 0x00: // common
275                     final long hcrc = buffer.getShort();
276                     a.add(String.format("  hCRC=0x%04X", hcrc & USHORT_MASK));
277                     try {
278                         for (int i = 0, n = length - 2; i < n; i++)
279                             a.add(String.format("  [%d]=%02X", i, buffer.get() & UBYTE_MASK));
280                     }
281                     catch (Exception ex) {
282                         warn(out, ">>> %s", ex);
283                         return buffer.position();
284                     }
285                     break;
286                 case 0x01: // file name
287                     x01.write(nextPath(length));
288                     break;
289                 case 0x02: // dir name
290                     x02.write(nextPath(length));
291                     break;
292                 case 0x41: // MS Windows timestamp
293                     final long wctime = buffer.getLong();
294                     final long wmtime = buffer.getLong();
295                     final long watime = buffer.getLong();
296                     Date wcd = new Date(FILETIME.toMilliseconds(wctime));
297                     Date wmd = new Date(FILETIME.toMilliseconds(wmtime));
298                     Date wad = new Date(FILETIME.toMilliseconds(watime));
299                     a.add(String.format("  Windows ctime = %s (%d)", wcd, wctime));
300                     a.add(String.format("  Windows mtime = %s (%d)", wmd, wmtime));
301                     a.add(String.format("  Windows atime = %s (%d)", wad, watime));
302                     break;
303                 case 0x42: // MS filesize
304                     a.add("  MS compsize = " + buffer.getLong());
305                     a.add("  MS filesize = " + buffer.getLong());
306                     break;
307                 case 0x54: // UNIX time_t
308                     final long umtime = buffer.getLong();
309                     Date umd = new Date(FILETIME.toMilliseconds(umtime));
310                     a.add(String.format("  UNIX mtime = %s (%d)", umd, umtime));
311                     break;
312                 default:
313                     for (int i = 0; i < length; i++)
314                         a.add(String.format("  [%d]=%02X", i, buffer.get() & UBYTE_MASK));
315             }
316             buffer.reset();
317             buffer.position(buffer.position() + length);
318             nextHeaderLength = buffer.getShort();
319             p = buffer.position();
320         }
321         final int headerLength0 = p;
322         ByteArrayOutputStream nameb = new ByteArrayOutputStream();
323         if (x02.size() > 0) {
324             nameb.write(x02.toByteArray());
325             if (!nameb.toString().endsWith("/"))
326                 nameb.write('/');
327         }
328         if (x01.size() > 0)
329             nameb.write(x01.toByteArray());
330         printHeaderName(out, "Level 2 Header");
331         p(out, "name", nameb.toByteArray());
332         p(out, "mtime as Date", new Date(TIME_T.toMilliseconds(mtime)));
333         p(out, "method", methodName);
334         p(out, "headerLength", headerLength);
335         p(out, "compressedSize", compressedSize);
336         p(out, "size", size);
337         p(out, "mtime", mtime);
338         p(out, "reserved", reserved);
339         p(out, "level", level);
340         p(out, "crc", crc);
341         p(out, "osIdentifier", (char)osIdentifier);
342         p(out, "firstExHdrLen", firstExHeaderLength);
343         for (final String x : a)
344             p(out, "  ", x);
345         if (headerLength != headerLength0)
346             warn(out, "header length is expected %d , but was %d%n", headerLength, p);
347         readLength += headerLength0;
348         if (compressedSize > 0)
349             readLength += is.skip(compressedSize);
350         return readLength;
351     }
352
353     private void p(PrintWriter out, String name, Object value) {
354         if (value instanceof Character) {
355             final char c = ((Character)value).charValue();
356             out.printf("  %-16s [%04X] ('%s')%n", name, (int)c, value);
357         }
358         else if (value instanceof Number) {
359             final long v;
360             final int w;
361             if (value instanceof Byte) {
362                 v = ((Byte)value) & 0xFF;
363                 w = 2;
364             }
365             else if (value instanceof Short) {
366                 v = ((Short)value) & 0xFFFF;
367                 w = 4;
368             }
369             else if (value instanceof Integer) {
370                 v = ((Integer)value) & 0xFFFFFFFFL;
371                 w = 8;
372             }
373             else {
374                 v = ((Number)value).longValue();
375                 w = 1;
376             }
377             out.printf("  %-16s [%0" + w + "X] (%d)%n", name, value, v);
378         }
379         else if (value instanceof byte[])
380             out.printf("  * %s = %s%n", name, new String((byte[])value));
381         else if (value instanceof Date)
382             out.printf("  * %s = %s%n", name, value);
383         else
384             out.printf("  %s %s%n", name, value);
385     }
386
387     private byte[] getBytes(int length) {
388         byte[] bytes = new byte[length];
389         for (int i = 0; i < bytes.length; i++)
390             bytes[i] = buffer.get();
391         return bytes;
392     }
393
394     private byte[] nextPath(int length) {
395         byte[] bytes = new byte[length];
396         buffer.get(bytes);
397         for (int i = 0; i < length; i++)
398             if ((bytes[i] & PATH_DELIMITER) == PATH_DELIMITER)
399                 bytes[i] = '/';
400         return bytes;
401     }
402
403     static Date toDate(int mdate, int mtime) {
404         final int ftime = (mdate << 16) | mtime & 0xFFFF;
405         return new Date(FTIME.toMilliseconds(ftime));
406     }
407
408 }