1 #! /usr/bin/env python3
3 """Dump archive contents, test extraction."""
5 from __future__
import division
, absolute_import
, print_function
11 from datetime
import datetime
17 dumprar [switches] [ARC1 ARC2 ...] [@ARCLIST]
19 @file read archive names from file
21 -Ccharset set fallback charset
23 -t attempt to read all files
24 -x write read files out
25 -c show archive comment
27 -- stop switch parsing
30 os_list
= ['DOS', 'OS2', 'WIN', 'UNIX', 'MACOS', 'BEOS']
32 block_strs
= ['MARK', 'MAIN', 'FILE', 'OLD_COMMENT', 'OLD_EXTRA',
33 'OLD_SUB', 'OLD_RECOVERY', 'OLD_AUTH', 'SUB', 'ENDARC']
36 rf
.RAR5_BLOCK_MAIN
: 'R5_MAIN',
37 rf
.RAR5_BLOCK_FILE
: 'R5_FILE',
38 rf
.RAR5_BLOCK_SERVICE
: 'R5_SVC',
39 rf
.RAR5_BLOCK_ENCRYPTION
: 'R5_ENC',
40 rf
.RAR5_BLOCK_ENDARC
: 'R5_ENDARC',
45 """RAR3 type code as string."""
46 if btype
< rf
.RAR_BLOCK_MARK
or btype
> rf
.RAR_BLOCK_ENDARC
:
48 return block_strs
[btype
- rf
.RAR_BLOCK_MARK
]
52 """RAR5 type code as string."""
53 return r5_block_types
.get(btype
, '*UNKNOWN*')
57 (rf
.RAR_MAIN_VOLUME
, "VOL"),
58 (rf
.RAR_MAIN_COMMENT
, "COMMENT"),
59 (rf
.RAR_MAIN_LOCK
, "LOCK"),
60 (rf
.RAR_MAIN_SOLID
, "SOLID"),
61 (rf
.RAR_MAIN_NEWNUMBERING
, "NEWNR"),
62 (rf
.RAR_MAIN_AUTH
, "AUTH"),
63 (rf
.RAR_MAIN_RECOVERY
, "RECOVERY"),
64 (rf
.RAR_MAIN_PASSWORD
, "PASSWORD"),
65 (rf
.RAR_MAIN_FIRSTVOLUME
, "FIRSTVOL"),
66 (rf
.RAR_SKIP_IF_UNKNOWN
, "SKIP"),
67 (rf
.RAR_LONG_BLOCK
, "LONG"),
71 (rf
.RAR_ENDARC_NEXT_VOLUME
, "NEXTVOL"),
72 (rf
.RAR_ENDARC_DATACRC
, "DATACRC"),
73 (rf
.RAR_ENDARC_REVSPACE
, "REVSPACE"),
74 (rf
.RAR_ENDARC_VOLNR
, "VOLNR"),
75 (rf
.RAR_SKIP_IF_UNKNOWN
, "SKIP"),
76 (rf
.RAR_LONG_BLOCK
, "LONG"),
80 (rf
.RAR_FILE_SPLIT_BEFORE
, "SPLIT_BEFORE"),
81 (rf
.RAR_FILE_SPLIT_AFTER
, "SPLIT_AFTER"),
82 (rf
.RAR_FILE_PASSWORD
, "PASSWORD"),
83 (rf
.RAR_FILE_COMMENT
, "COMMENT"),
84 (rf
.RAR_FILE_SOLID
, "SOLID"),
85 (rf
.RAR_FILE_LARGE
, "LARGE"),
86 (rf
.RAR_FILE_UNICODE
, "UNICODE"),
87 (rf
.RAR_FILE_SALT
, "SALT"),
88 (rf
.RAR_FILE_VERSION
, "VERSION"),
89 (rf
.RAR_FILE_EXTTIME
, "EXTTIME"),
90 (rf
.RAR_FILE_EXTFLAGS
, "EXTFLAGS"),
91 (rf
.RAR_SKIP_IF_UNKNOWN
, "SKIP"),
92 (rf
.RAR_LONG_BLOCK
, "LONG"),
96 (rf
.RAR_SKIP_IF_UNKNOWN
, "SKIP"),
97 (rf
.RAR_LONG_BLOCK
, "LONG"),
100 file_parms
= ("D64", "D128", "D256", "D512",
101 "D1024", "D2048", "D4096", "DIR")
104 (rf
.RAR5_BLOCK_FLAG_EXTRA_DATA
, 'EXTRA'),
105 (rf
.RAR5_BLOCK_FLAG_DATA_AREA
, 'DATA'),
106 (rf
.RAR5_BLOCK_FLAG_SKIP_IF_UNKNOWN
, 'SKIP'),
107 (rf
.RAR5_BLOCK_FLAG_SPLIT_BEFORE
, 'SPLIT_BEFORE'),
108 (rf
.RAR5_BLOCK_FLAG_SPLIT_AFTER
, 'SPLIT_AFTER'),
109 (rf
.RAR5_BLOCK_FLAG_DEPENDS_PREV
, 'DEPENDS'),
110 (rf
.RAR5_BLOCK_FLAG_KEEP_WITH_PARENT
, 'KEEP'),
114 (rf
.RAR5_MAIN_FLAG_ISVOL
, 'ISVOL'),
115 (rf
.RAR5_MAIN_FLAG_HAS_VOLNR
, 'VOLNR'),
116 (rf
.RAR5_MAIN_FLAG_SOLID
, 'SOLID'),
117 (rf
.RAR5_MAIN_FLAG_RECOVERY
, 'RECOVERY'),
118 (rf
.RAR5_MAIN_FLAG_LOCKED
, 'LOCKED'),
122 (rf
.RAR5_FILE_FLAG_ISDIR
, 'DIR'),
123 (rf
.RAR5_FILE_FLAG_HAS_MTIME
, 'MTIME'),
124 (rf
.RAR5_FILE_FLAG_HAS_CRC32
, 'CRC32'),
125 (rf
.RAR5_FILE_FLAG_UNKNOWN_SIZE
, 'NOSIZE'),
129 (rf
.RAR5_ENC_FLAG_HAS_CHECKVAL
, 'CHECKVAL'),
133 (rf
.RAR5_ENDARC_FLAG_NEXT_VOL
, 'NEXTVOL'),
136 r5_file_enc_flags
= (
137 (rf
.RAR5_XENC_CHECKVAL
, 'CHECKVAL'),
138 (rf
.RAR5_XENC_TWEAKED
, 'TWEAKED'),
141 r5_file_redir_types
= {
142 rf
.RAR5_XREDIR_UNIX_SYMLINK
: 'UNIX_SYMLINK',
143 rf
.RAR5_XREDIR_WINDOWS_SYMLINK
: 'WINDOWS_SYMLINK',
144 rf
.RAR5_XREDIR_WINDOWS_JUNCTION
: 'WINDOWS_JUNCTION',
145 rf
.RAR5_XREDIR_HARD_LINK
: 'HARD_LINK',
146 rf
.RAR5_XREDIR_FILE_COPY
: 'FILE_COPY',
149 r5_file_redir_flags
= (
150 (rf
.RAR5_XREDIR_ISDIR
, 'DIR'),
154 def xprint(m
, *args
):
155 """Print string to stdout.
157 Format unicode safely.
159 if sys
.hexversion
< 0x3000000:
163 if sys
.hexversion
< 0x3000000:
166 sys
.stdout
.write('\n')
169 def render_flags(flags
, bit_list
):
175 known
= known | bit
[0]
178 unknown
= flags
& ~known
182 res
.append("UNK_%04x" % (1 << n
))
183 unknown
= unknown
>> 1
192 def get_file_flags(flags
):
193 """Show flag names and handle dict size.
195 res
= render_flags(flags
& ~rf
.RAR_FILE_DICTMASK
, file_bits
)
197 xf
= (flags
& rf
.RAR_FILE_DICTMASK
) >> 5
198 res
+= "," + file_parms
[xf
]
207 if isinstance(t
, datetime
):
208 return t
.isoformat('T')
209 return "%04d-%02d-%02d %02d:%02d:%02d" % t
213 """Show any RAR3/5 record.
215 if isinstance(h
, rf
.Rar3Info
):
217 elif isinstance(h
, rf
.Rar5Info
):
220 xprint('Unknown info record')
224 """Show any RAR3 record.
226 st
= rar3_type(h
.type)
227 xprint("%s: hdrlen=%d datlen=%d", st
, h
.header_size
, h
.add_size
)
228 if h
.type in (rf
.RAR_BLOCK_FILE
, rf
.RAR_BLOCK_SUB
):
229 if h
.host_os
== rf
.RAR_OS_UNIX
:
230 s_mode
= "0%o" % h
.mode
232 s_mode
= "0x%x" % h
.mode
233 xprint(" flags=0x%04x:%s", h
.flags
, get_file_flags(h
.flags
))
234 if h
.host_os
>= 0 and h
.host_os
< len(os_list
):
235 s_os
= os_list
[h
.host_os
]
238 xprint(" os=%d:%s ver=%d mode=%s meth=%c cmp=%d dec=%d vol=%d",
240 h
.extract_version
, s_mode
, h
.compress_type
,
241 h
.compress_size
, h
.file_size
, h
.volume
)
242 ucrc
= (h
.CRC
+ (1 << 32)) & ((1 << 32) - 1)
243 xprint(" crc=0x%08x (%d) date_time=%s", ucrc
, h
.CRC
, fmt_time(h
.date_time
))
244 xprint(" name=%s", h
.filename
)
246 xprint(" mtime=%s", fmt_time(h
.mtime
))
248 xprint(" ctime=%s", fmt_time(h
.ctime
))
250 xprint(" atime=%s", fmt_time(h
.atime
))
252 xprint(" arctime=%s", fmt_time(h
.arctime
))
253 elif h
.type == rf
.RAR_BLOCK_MAIN
:
254 xprint(" flags=0x%04x:%s", h
.flags
, render_flags(h
.flags
, main_bits
))
255 elif h
.type == rf
.RAR_BLOCK_ENDARC
:
256 xprint(" flags=0x%04x:%s", h
.flags
, render_flags(h
.flags
, endarc_bits
))
257 elif h
.type == rf
.RAR_BLOCK_MARK
:
258 xprint(" flags=0x%04x:", h
.flags
)
260 xprint(" flags=0x%04x:%s", h
.flags
, render_flags(h
.flags
, generic_bits
))
262 if h
.comment
is not None:
266 xprint(" comment=%s", cm
)
270 """Show any RAR5 record.
272 st
= rar5_type(h
.block_type
)
273 xprint("%s: hdrlen=%d datlen=%d hdr_extra=%d", st
, h
.header_size
,
274 h
.compress_size
, h
.block_extra_size
)
275 xprint(" block_flags=0x%04x:%s", h
.block_flags
, render_flags(h
.block_flags
, r5_block_flags
))
276 if h
.block_type
in (rf
.RAR5_BLOCK_FILE
, rf
.RAR5_BLOCK_SERVICE
):
277 xprint(" name=%s", h
.filename
)
278 if h
.file_host_os
== rf
.RAR5_OS_UNIX
:
280 s_mode
= "0%o" % h
.mode
283 s_mode
= "0x%x" % h
.mode
284 xprint(" file_flags=0x%04x:%s", h
.file_flags
, render_flags(h
.file_flags
, r5_file_flags
))
286 cmp_flags
= h
.file_compress_flags
287 xprint(" cmp_algo=%d cmp_meth=%d dict=%d solid=%r",
289 (cmp_flags
>> 7) & 0x07,
291 cmp_flags
& rf
.RAR5_COMPR_SOLID
> 0)
292 xprint(" os=%d:%s mode=%s cmp=%r dec=%r vol=%r",
293 h
.file_host_os
, s_os
, s_mode
,
294 h
.compress_size
, h
.file_size
, h
.volume
)
295 if h
.CRC
is not None:
296 xprint(" crc=0x%08x (%d)", h
.CRC
, h
.CRC
)
297 if h
.blake2sp_hash
is not None:
298 xprint(" blake2sp=%s", rf
.tohex(h
.blake2sp_hash
))
299 if h
.date_time
is not None:
300 xprint(" date_time=%s", fmt_time(h
.date_time
))
302 xprint(" mtime=%s", fmt_time(h
.mtime
))
304 xprint(" ctime=%s", fmt_time(h
.ctime
))
306 xprint(" atime=%s", fmt_time(h
.atime
))
308 xprint(" arctime=%s", fmt_time(h
.arctime
))
309 if h
.flags
& rf
.RAR_FILE_PASSWORD
:
310 enc_algo
, enc_flags
, kdf_count
, salt
, iv
, checkval
= h
.file_encryption
311 algo_name
= 'AES256' if enc_algo
== rf
.RAR5_XENC_CIPHER_AES256
else 'UnknownAlgo'
312 xprint(' algo=%d:%s enc_flags=%04x:%s kdf_lg=%d kdf_count=%d salt=%s iv=%s checkval=%s',
313 enc_algo
, algo_name
, enc_flags
, render_flags(enc_flags
, r5_file_enc_flags
),
314 kdf_count
, 1 << kdf_count
, rf
.tohex(salt
), rf
.tohex(iv
),
315 checkval
and rf
.tohex(checkval
) or '-')
317 redir_type
, redir_flags
, redir_name
= h
.file_redir
318 xprint(' redir: type=%s flags=%d:%s destination=%s',
319 r5_file_redir_types
.get(redir_type
, 'Unknown'),
320 redir_flags
, render_flags(redir_flags
, r5_file_redir_flags
),
323 uname
, gname
, uid
, gid
= h
.file_owner
324 xprint(' owner: name=%r group=%r uid=%r gid=%r',
325 uname
, gname
, uid
, gid
)
327 flags
, version
= h
.file_version
328 xprint(' version: flags=%r version=%r', flags
, version
)
329 elif h
.block_type
== rf
.RAR5_BLOCK_MAIN
:
330 xprint(" flags=0x%04x:%s", h
.flags
, render_flags(h
.main_flags
, r5_main_flags
))
331 elif h
.block_type
== rf
.RAR5_BLOCK_ENDARC
:
332 xprint(" flags=0x%04x:%s", h
.flags
, render_flags(h
.endarc_flags
, r5_endarc_flags
))
333 elif h
.block_type
== rf
.RAR5_BLOCK_ENCRYPTION
:
334 algo_name
= 'AES256' if h
.encryption_algo
== rf
.RAR5_XENC_CIPHER_AES256
else 'UnknownAlgo'
335 xprint(" algo=%d:%s flags=0x%04x:%s", h
.encryption_algo
, algo_name
, h
.flags
,
336 render_flags(h
.encryption_flags
, r5_enc_flags
))
337 xprint(" kdf_lg=%d kdf_count=%d", h
.encryption_kdf_count
, 1 << h
.encryption_kdf_count
)
338 xprint(" salt=%s", rf
.tohex(h
.encryption_salt
))
340 xprint(" - missing info -")
342 if h
.comment
is not None:
346 xprint(" comment=%s", cm
)
358 def check_crc(f
, inf
, desc
):
359 """Compare result crc to expected value.
364 ucrc
= f
._md
_context
.digest()
366 print('crc error - %s - exp=%r got=%r' % (desc
, exp
, ucrc
))
369 def test_read_long(r
, inf
):
370 """Test read and readinto.
372 md_class
= inf
._md
_class
or rf
.NoHashContext
374 f
= r
.open(inf
.filename
)
382 if total
!= inf
.file_size
:
383 xprint("\n *** %s has corrupt file: %s ***", r
.rarfile
, inf
.filename
)
384 xprint(" *** short read: got=%d, need=%d ***\n", total
, inf
.file_size
)
385 check_crc(f
, inf
, 'read')
386 bhash
= bctx
.hexdigest()
388 if f
._md
_context
.digest() == inf
._md
_expect
:
389 #xprint(" checkhash: %r", bhash)
392 xprint(" checkhash: %r got=%r exp=%r cls=%r\n",
393 bhash
, f
._md
_context
.digest(), inf
._md
_expect
, inf
._md
_class
)
395 # test .seek() & .readinto()
400 buf
= bytearray(rf
.ZERO
* 1024)
402 res
= f
.readinto(buf
)
406 if inf
.file_size
!= total
:
407 xprint(" *** readinto failed: got=%d, need=%d ***\n", total
, inf
.file_size
)
408 #check_crc(f, inf, 'readinto')
412 def test_read(r
, inf
):
413 """Test file read."""
414 test_read_long(r
, inf
)
417 def test_real(fn
, psw
):
418 """Actual archive processing.
420 xprint("Archive: %s", fn
)
428 rfarg
= io
.BytesIO(open(fn
, 'rb').read())
431 if not rf
.is_rarfile(rfarg
):
432 xprint(" --- %s is not a RAR file ---", fn
)
436 r
= rf
.RarFile(rfarg
, charset
=cf_charset
, info_callback
=cb
)
438 if r
.needs_password():
442 xprint(" --- %s requires password ---", fn
)
446 if cf_show_comment
and r
.comment
:
447 for ln
in r
.comment
.split('\n'):
449 elif cf_verbose
> 0 and r
.comment
:
453 xprint(" comment=%s", cm
)
456 for n
in r
.namelist():
467 for inf
in r
.infolist():
475 """Process one archive with error handling.
479 except rf
.NeedFirstVolume
:
480 xprint(" --- %s is middle part of multi-vol archive ---", fn
)
482 exc
, msg
, tb
= sys
.exc_info()
483 xprint("\n *** %s: %s ***\n", exc
.__name
__, msg
)
486 exc
, msg
, tb
= sys
.exc_info()
487 xprint("\n *** %s: %s ***\n", exc
.__name
__, msg
)
492 """Program entry point.
494 global cf_verbose
, cf_show_comment
, cf_charset
495 global cf_extract
, cf_test_read
, cf_test_unrar
496 global cf_test_memory
502 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'p:C:hvcxtRM')
503 except getopt
.error
as ex
:
504 print(str(ex
), file=sys
.stderr
)
528 raise Exception("unhandled switch: " + o
)
533 for ln
in open(a
[1:], 'r'):
543 # pypy .readinto()+memoryview() is buggy
544 #if cf_test_read > 1 and hasattr(sys, 'pypy_version_info'):
551 if __name__
== '__main__':
554 except KeyboardInterrupt: