1 #! /usr/bin/env python3
3 """Dump archive contents, test extraction."""
9 from datetime
import datetime
14 dumprar [switches] [ARC1 ARC2 ...] [@ARCLIST]
16 @file read archive names from file
18 -Ccharset set fallback charset
20 -t attempt to read all files
21 -x write read files out
22 -c show archive comment
24 -bTOOL set backend tool (unrar, unar, bsdtar, 7z, 7zz)
25 -- stop switch parsing
28 os_list
= ["DOS", "OS2", "WIN", "UNIX", "MACOS", "BEOS"]
30 block_strs
= ["MARK", "MAIN", "FILE", "OLD_COMMENT", "OLD_EXTRA",
31 "OLD_SUB", "OLD_RECOVERY", "OLD_AUTH", "SUB", "ENDARC"]
34 rf
.RAR5_BLOCK_MAIN
: "R5_MAIN",
35 rf
.RAR5_BLOCK_FILE
: "R5_FILE",
36 rf
.RAR5_BLOCK_SERVICE
: "R5_SVC",
37 rf
.RAR5_BLOCK_ENCRYPTION
: "R5_ENC",
38 rf
.RAR5_BLOCK_ENDARC
: "R5_ENDARC",
43 """RAR3 type code as string."""
44 if btype
< rf
.RAR_BLOCK_MARK
or btype
> rf
.RAR_BLOCK_ENDARC
:
46 return block_strs
[btype
- rf
.RAR_BLOCK_MARK
]
50 """RAR5 type code as string."""
51 return r5_block_types
.get(btype
, "*UNKNOWN*")
55 (rf
.RAR_MAIN_VOLUME
, "VOL"),
56 (rf
.RAR_MAIN_COMMENT
, "COMMENT"),
57 (rf
.RAR_MAIN_LOCK
, "LOCK"),
58 (rf
.RAR_MAIN_SOLID
, "SOLID"),
59 (rf
.RAR_MAIN_NEWNUMBERING
, "NEWNR"),
60 (rf
.RAR_MAIN_AUTH
, "AUTH"),
61 (rf
.RAR_MAIN_RECOVERY
, "RECOVERY"),
62 (rf
.RAR_MAIN_PASSWORD
, "PASSWORD"),
63 (rf
.RAR_MAIN_FIRSTVOLUME
, "FIRSTVOL"),
64 (rf
.RAR_SKIP_IF_UNKNOWN
, "SKIP"),
65 (rf
.RAR_LONG_BLOCK
, "LONG"),
69 (rf
.RAR_ENDARC_NEXT_VOLUME
, "NEXTVOL"),
70 (rf
.RAR_ENDARC_DATACRC
, "DATACRC"),
71 (rf
.RAR_ENDARC_REVSPACE
, "REVSPACE"),
72 (rf
.RAR_ENDARC_VOLNR
, "VOLNR"),
73 (rf
.RAR_SKIP_IF_UNKNOWN
, "SKIP"),
74 (rf
.RAR_LONG_BLOCK
, "LONG"),
78 (rf
.RAR_FILE_SPLIT_BEFORE
, "SPLIT_BEFORE"),
79 (rf
.RAR_FILE_SPLIT_AFTER
, "SPLIT_AFTER"),
80 (rf
.RAR_FILE_PASSWORD
, "PASSWORD"),
81 (rf
.RAR_FILE_COMMENT
, "COMMENT"),
82 (rf
.RAR_FILE_SOLID
, "SOLID"),
83 (rf
.RAR_FILE_LARGE
, "LARGE"),
84 (rf
.RAR_FILE_UNICODE
, "UNICODE"),
85 (rf
.RAR_FILE_SALT
, "SALT"),
86 (rf
.RAR_FILE_VERSION
, "VERSION"),
87 (rf
.RAR_FILE_EXTTIME
, "EXTTIME"),
88 (rf
.RAR_FILE_EXTFLAGS
, "EXTFLAGS"),
89 (rf
.RAR_SKIP_IF_UNKNOWN
, "SKIP"),
90 (rf
.RAR_LONG_BLOCK
, "LONG"),
94 (rf
.RAR_SKIP_IF_UNKNOWN
, "SKIP"),
95 (rf
.RAR_LONG_BLOCK
, "LONG"),
98 file_parms
= ("D64", "D128", "D256", "D512",
99 "D1024", "D2048", "D4096", "DIR")
102 (rf
.RAR5_BLOCK_FLAG_EXTRA_DATA
, "EXTRA"),
103 (rf
.RAR5_BLOCK_FLAG_DATA_AREA
, "DATA"),
104 (rf
.RAR5_BLOCK_FLAG_SKIP_IF_UNKNOWN
, "SKIP"),
105 (rf
.RAR5_BLOCK_FLAG_SPLIT_BEFORE
, "SPLIT_BEFORE"),
106 (rf
.RAR5_BLOCK_FLAG_SPLIT_AFTER
, "SPLIT_AFTER"),
107 (rf
.RAR5_BLOCK_FLAG_DEPENDS_PREV
, "DEPENDS"),
108 (rf
.RAR5_BLOCK_FLAG_KEEP_WITH_PARENT
, "KEEP"),
112 (rf
.RAR5_MAIN_FLAG_ISVOL
, "ISVOL"),
113 (rf
.RAR5_MAIN_FLAG_HAS_VOLNR
, "VOLNR"),
114 (rf
.RAR5_MAIN_FLAG_SOLID
, "SOLID"),
115 (rf
.RAR5_MAIN_FLAG_RECOVERY
, "RECOVERY"),
116 (rf
.RAR5_MAIN_FLAG_LOCKED
, "LOCKED"),
120 (rf
.RAR5_FILE_FLAG_ISDIR
, "DIR"),
121 (rf
.RAR5_FILE_FLAG_HAS_MTIME
, "MTIME"),
122 (rf
.RAR5_FILE_FLAG_HAS_CRC32
, "CRC32"),
123 (rf
.RAR5_FILE_FLAG_UNKNOWN_SIZE
, "NOSIZE"),
127 (rf
.RAR5_ENC_FLAG_HAS_CHECKVAL
, "CHECKVAL"),
131 (rf
.RAR5_ENDARC_FLAG_NEXT_VOL
, "NEXTVOL"),
134 r5_file_enc_flags
= (
135 (rf
.RAR5_XENC_CHECKVAL
, "CHECKVAL"),
136 (rf
.RAR5_XENC_TWEAKED
, "TWEAKED"),
139 r5_file_redir_types
= {
140 rf
.RAR5_XREDIR_UNIX_SYMLINK
: "UNIX_SYMLINK",
141 rf
.RAR5_XREDIR_WINDOWS_SYMLINK
: "WINDOWS_SYMLINK",
142 rf
.RAR5_XREDIR_WINDOWS_JUNCTION
: "WINDOWS_JUNCTION",
143 rf
.RAR5_XREDIR_HARD_LINK
: "HARD_LINK",
144 rf
.RAR5_XREDIR_FILE_COPY
: "FILE_COPY",
147 r5_file_redir_flags
= (
148 (rf
.RAR5_XREDIR_ISDIR
, "DIR"),
161 (0x0100, "TEMPORARY"),
162 (0x0200, "SPARSE_FILE"),
163 (0x0400, "REPARSE_POINT"),
164 (0x0800, "COMPRESSED"),
166 (0x2000, "NOT_CONTENT_INDEXED"),
167 (0x4000, "ENCRYPTED"),
168 (0x8000, "INTEGRITY_STREAM"),
169 (0x00010000, "VIRTUAL"),
170 (0x00020000, "NO_SCRUB_DATA"),
171 (0x00040000, "RECALL_ON_OPEN"),
172 (0x00080000, "PINNED"),
173 (0x00100000, "UNPINNED"),
174 (0x00400000, "RECALL_ON_DATA_ACCESS"),
175 (0x20000000, "STRICTLY_SEQUENTIAL"),
179 def xprint(m
, *args
):
180 """Print string to stdout.
188 """Return hex string."""
189 return binascii
.hexlify(data
).decode("ascii")
192 def render_flags(flags
, bit_list
):
198 known
= known | bit
[0]
201 unknown
= flags
& ~known
205 res
.append("UNK_%04x" % (1 << n
))
206 unknown
= unknown
>> 1
215 def get_file_flags(flags
):
216 """Show flag names and handle dict size.
218 res
= render_flags(flags
& ~rf
.RAR_FILE_DICTMASK
, file_bits
)
220 xf
= (flags
& rf
.RAR_FILE_DICTMASK
) >> 5
221 res
+= "," + file_parms
[xf
]
230 if isinstance(t
, datetime
):
231 return t
.isoformat("T")
232 return "%04d-%02d-%02d %02d:%02d:%02d" % t
236 """Show any RAR3/5 record.
238 if isinstance(h
, rf
.Rar3Info
):
240 elif isinstance(h
, rf
.Rar5Info
):
243 xprint("Unknown info record")
248 h
.is_file() and "F" or "-",
249 h
.is_dir() and "D" or "-",
250 h
.is_symlink() and "L" or "-",
255 return [v
& 4 and "r" or "-", v
& 2 and "w" or "-", v
& 1 and "x" or "-"]
259 perms
= modex3(mode
>> 6) + modex3(mode
>> 3) + modex3(mode
)
261 perms
[2] = perms
[2] == "x" and "s" or "S"
263 perms
[5] = perms
[5] == "x" and "s" or "S"
265 perms
[8] = perms
[8] == "x" and "t" or "-"
278 perms
.append("(0x%04x)" % rest
)
279 return "".join(perms
)
283 if h
.host_os
in (rf
.RAR_OS_UNIX
, rf
.RAR_OS_BEOS
):
284 s_mode
= unix_mode(h
.mode
)
285 elif h
.host_os
in (rf
.RAR_OS_MSDOS
, rf
.RAR_OS_WIN32
, rf
.RAR_OS_OS2
):
286 s_mode
= render_flags(h
.mode
, dos_mode_bits
)
288 s_mode
= "0x%x" % h
.mode
293 """Show any RAR3 record.
295 st
= rar3_type(h
.type)
296 xprint("%s: hdrlen=%d datlen=%d is=%s",
297 st
, h
.header_size
, h
.add_size
, show_rftype(h
))
298 if h
.type in (rf
.RAR_BLOCK_FILE
, rf
.RAR_BLOCK_SUB
):
299 s_mode
= show_mode(h
)
300 xprint(" flags=0x%04x:%s", h
.flags
, get_file_flags(h
.flags
))
301 if h
.host_os
>= 0 and h
.host_os
< len(os_list
):
302 s_os
= os_list
[h
.host_os
]
305 if h
.flags
& rf
.RAR_FILE_UNICODE
:
306 s_namecmp
= " namecmp=%d/%d" % (len(h
.orig_filename
), h
._name
_size
)
309 xprint(" os=%d:%s ver=%d mode=%s meth=%c cmp=%d dec=%d vol=%d%s",
311 h
.extract_version
, s_mode
, h
.compress_type
,
312 h
.compress_size
, h
.file_size
, h
.volume
, s_namecmp
)
313 ucrc
= (h
.CRC
+ (1 << 32)) & ((1 << 32) - 1)
314 xprint(" crc=0x%08x (%d) date_time=%s", ucrc
, h
.CRC
, fmt_time(h
.date_time
))
315 xprint(" name=%s", h
.filename
)
317 xprint(" mtime=%s", fmt_time(h
.mtime
))
319 xprint(" ctime=%s", fmt_time(h
.ctime
))
321 xprint(" atime=%s", fmt_time(h
.atime
))
323 xprint(" arctime=%s", fmt_time(h
.arctime
))
324 elif h
.type == rf
.RAR_BLOCK_MAIN
:
325 xprint(" flags=0x%04x:%s", h
.flags
, render_flags(h
.flags
, main_bits
))
326 elif h
.type == rf
.RAR_BLOCK_ENDARC
:
327 xprint(" flags=0x%04x:%s", h
.flags
, render_flags(h
.flags
, endarc_bits
))
328 if h
.flags
& rf
.RAR_ENDARC_DATACRC
:
329 xprint(" datacrc=0x%08x", h
.endarc_datacrc
)
330 if h
.flags
& rf
.RAR_ENDARC_DATACRC
:
331 xprint(" volnr=%d", h
.endarc_volnr
)
332 elif h
.type == rf
.RAR_BLOCK_MARK
:
333 xprint(" flags=0x%04x:", h
.flags
)
335 xprint(" flags=0x%04x:%s", h
.flags
, render_flags(h
.flags
, generic_bits
))
337 if h
.comment
is not None:
341 xprint(" comment=%s", cm
)
345 """Show any RAR5 record.
347 st
= rar5_type(h
.block_type
)
348 xprint("%s: hdrlen=%d datlen=%d hdr_extra=%d is=%s", st
, h
.header_size
,
349 h
.compress_size
, h
.block_extra_size
, show_rftype(h
))
350 xprint(" block_flags=0x%04x:%s", h
.block_flags
, render_flags(h
.block_flags
, r5_block_flags
))
351 if h
.block_type
in (rf
.RAR5_BLOCK_FILE
, rf
.RAR5_BLOCK_SERVICE
):
352 xprint(" name=%s", h
.filename
)
353 s_mode
= show_mode(h
)
354 if h
.file_host_os
== rf
.RAR5_OS_UNIX
:
358 xprint(" file_flags=0x%04x:%s", h
.file_flags
, render_flags(h
.file_flags
, r5_file_flags
))
360 cmp_flags
= h
.file_compress_flags
361 xprint(" cmp_algo=%d cmp_meth=%d dict=%d solid=%r",
363 (cmp_flags
>> 7) & 0x07,
365 cmp_flags
& rf
.RAR5_COMPR_SOLID
> 0)
366 xprint(" os=%d:%s mode=%s cmp=%r dec=%r vol=%r",
367 h
.file_host_os
, s_os
, s_mode
,
368 h
.compress_size
, h
.file_size
, h
.volume
)
369 if h
.CRC
is not None:
370 xprint(" crc=0x%08x (%d)", h
.CRC
, h
.CRC
)
371 if h
.blake2sp_hash
is not None:
372 xprint(" blake2sp=%s", tohex(h
.blake2sp_hash
))
373 if h
.date_time
is not None:
374 xprint(" date_time=%s", fmt_time(h
.date_time
))
376 xprint(" mtime=%s", fmt_time(h
.mtime
))
378 xprint(" ctime=%s", fmt_time(h
.ctime
))
380 xprint(" atime=%s", fmt_time(h
.atime
))
382 xprint(" arctime=%s", fmt_time(h
.arctime
))
383 if h
.flags
& rf
.RAR_FILE_PASSWORD
:
384 enc_algo
, enc_flags
, kdf_count
, salt
, iv
, checkval
= h
.file_encryption
385 algo_name
= "AES256" if enc_algo
== rf
.RAR5_XENC_CIPHER_AES256
else "UnknownAlgo"
386 xprint(" algo=%d:%s enc_flags=%04x:%s kdf_lg=%d kdf_count=%d salt=%s iv=%s checkval=%s",
387 enc_algo
, algo_name
, enc_flags
, render_flags(enc_flags
, r5_file_enc_flags
),
388 kdf_count
, 1 << kdf_count
, tohex(salt
), tohex(iv
),
389 checkval
and tohex(checkval
) or "-")
391 redir_type
, redir_flags
, redir_name
= h
.file_redir
392 xprint(" redir: type=%s flags=%d:%s destination=%s",
393 r5_file_redir_types
.get(redir_type
, "Unknown"),
394 redir_flags
, render_flags(redir_flags
, r5_file_redir_flags
),
397 uname
, gname
, uid
, gid
= h
.file_owner
398 xprint(" owner: name=%r group=%r uid=%r gid=%r",
399 uname
, gname
, uid
, gid
)
401 flags
, version
= h
.file_version
402 xprint(" version: flags=%r version=%r", flags
, version
)
403 elif h
.block_type
== rf
.RAR5_BLOCK_MAIN
:
404 xprint(" flags=0x%04x:%s", h
.flags
, render_flags(h
.main_flags
, r5_main_flags
))
405 elif h
.block_type
== rf
.RAR5_BLOCK_ENDARC
:
406 xprint(" flags=0x%04x:%s", h
.flags
, render_flags(h
.endarc_flags
, r5_endarc_flags
))
407 elif h
.block_type
== rf
.RAR5_BLOCK_ENCRYPTION
:
408 algo_name
= "AES256" if h
.encryption_algo
== rf
.RAR5_XENC_CIPHER_AES256
else "UnknownAlgo"
409 xprint(" algo=%d:%s flags=0x%04x:%s", h
.encryption_algo
, algo_name
, h
.flags
,
410 render_flags(h
.encryption_flags
, r5_enc_flags
))
411 xprint(" kdf_lg=%d kdf_count=%d", h
.encryption_kdf_count
, 1 << h
.encryption_kdf_count
)
412 xprint(" salt=%s", tohex(h
.encryption_salt
))
414 xprint(" - missing info -")
416 if h
.comment
is not None:
420 xprint(" comment=%s", cm
)
432 def check_crc(f
, inf
, desc
):
433 """Compare result crc to expected value.
438 ucrc
= f
._md
_context
.digest()
440 print("crc error - %s - exp=%r got=%r" % (desc
, exp
, ucrc
))
443 def test_read_long(r
, inf
):
444 """Test read and readinto.
446 md_class
= inf
._md
_class
or rf
.NoHashContext
448 inf_orig
= r
.getinfo_orig(inf
.filename
)
449 f
= r
.open(inf
.filename
)
457 if total
!= inf
.file_size
:
458 xprint("\n *** %s has corrupt file: %s ***", r
.rarfile
, inf
.filename
)
459 xprint(" *** short read: got=%d, need=%d ***\n", total
, inf
.file_size
)
460 check_crc(f
, inf_orig
, "read")
461 bhash
= bctx
.hexdigest()
463 if f
._md
_context
.digest() == inf_orig
._md
_expect
:
464 #xprint(" checkhash: %r", bhash)
467 xprint(" checkhash: %r got=%r exp=%r cls=%r\n",
468 bhash
, f
._md
_context
.digest(), inf
._md
_expect
, inf
._md
_class
)
470 # test .seek() & .readinto()
475 buf
= bytearray(1024)
477 res
= f
.readinto(buf
)
481 if inf
.file_size
!= total
:
482 xprint(" *** readinto failed: got=%d, need=%d ***\n", total
, inf
.file_size
)
483 #check_crc(f, inf, "readinto")
487 def test_read(r
, inf
):
488 """Test file read."""
489 test_read_long(r
, inf
)
492 def test_real(fn
, pwd
):
493 """Actual archive processing.
495 xprint("Archive: %s", fn
)
503 rfarg
= io
.BytesIO(open(fn
, "rb").read())
506 if not rf
.is_rarfile(rfarg
):
507 xprint(" --- %s is not a RAR file ---", fn
)
511 r
= rf
.RarFile(rfarg
, charset
=cf_charset
, info_callback
=cb
)
513 if r
.needs_password():
517 xprint(" --- %s requires password ---", fn
)
521 if cf_show_comment
and r
.comment
:
522 for ln
in r
.comment
.split("\n"):
524 elif cf_verbose
> 0 and r
.comment
:
528 xprint(" comment=%s", cm
)
531 for n
in r
.namelist():
535 if cf_test_read
and inf
.is_file():
540 for inf
in r
.infolist():
548 """Process one archive with error handling.
552 except rf
.NeedFirstVolume
as ex
:
553 xprint(" --- %s is middle part of multi-vol archive (%s)---", fn
, str(ex
))
555 exc
, msg
, tb
= sys
.exc_info()
556 xprint("\n *** %s: %s ***\n", exc
.__name
__, msg
)
559 exc
, msg
, tb
= sys
.exc_info()
560 xprint("\n *** %s: %s ***\n", exc
.__name
__, msg
)
565 """Program entry point.
567 global cf_verbose
, cf_show_comment
, cf_charset
568 global cf_extract
, cf_test_read
, cf_test_unrar
569 global cf_test_memory
576 opts
, args
= getopt
.getopt(sys
.argv
[1:], "p:C:hvcxtRMb:")
577 except getopt
.error
as ex
:
578 print(str(ex
), file=sys
.stderr
)
604 raise ValueError("unhandled switch: " + o
)
609 for ln
in open(a
[1:], "r", encoding
="utf8"):
620 cf_backend
= {"7z": "sevenzip", "7zz": "sevenzip2"}.get(cf_backend
, cf_backend
)
621 conf
= {"unrar": False, "unar": False, "bsdtar": False, "sevenzip": False, "sevenzip2": False}
622 assert cf_backend
in conf
, f
"unknown backend: {cf_backend}"
623 conf
[cf_backend
] = True
624 rf
.tool_setup(force
=True, **conf
)
630 if __name__
== "__main__":
633 except KeyboardInterrupt: