Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rarfile.py: 34%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# rarfile.py
2#
3# Copyright (c) 2005-2026 Marko Kreen <markokr@gmail.com>
4#
5# Permission to use, copy, modify, and/or distribute this software for any
6# purpose with or without fee is hereby granted, provided that the above
7# copyright notice and this permission notice appear in all copies.
8#
9# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17"""RAR archive reader.
19This is Python module for Rar archive reading. The interface
20is made as :mod:`zipfile`-like as possible.
22Basic logic:
23 - Parse archive structure with Python.
24 - Extract non-compressed files with Python
25 - Extract compressed files with unrar.
26 - Optionally write compressed data to temp file to speed up unrar,
27 otherwise it needs to scan whole archive on each execution.
29Example::
31 import rarfile
33 rf = rarfile.RarFile("myarchive.rar")
34 for f in rf.infolist():
35 print(f.filename, f.file_size)
36 if f.filename == "README":
37 print(rf.read(f))
39Archive files can also be accessed via file-like object returned
40by :meth:`RarFile.open`::
42 import rarfile
44 with rarfile.RarFile("archive.rar") as rf:
45 with rf.open("README") as f:
46 for ln in f:
47 print(ln.strip())
49For decompression to work, either ``unrar`` or ``unar`` tool must be in PATH.
50"""
52import errno
53import io
54import os
55import re
56import shutil
57import struct
58import sys
59import warnings
60from binascii import crc32, hexlify
61from datetime import datetime, timezone
62from hashlib import blake2s, pbkdf2_hmac, sha1, sha256
63from pathlib import Path
64from struct import Struct, pack, unpack
65from subprocess import DEVNULL, PIPE, STDOUT, Popen
66from tempfile import mkstemp
68AES = None
70# only needed for encrypted headers
71try:
72 try:
73 from cryptography.hazmat.backends import default_backend
74 from cryptography.hazmat.primitives.ciphers import (
75 Cipher, algorithms, modes,
76 )
77 _have_crypto = 1
78 except ImportError:
79 from Crypto.Cipher import AES
80 _have_crypto = 2
81except ImportError:
82 _have_crypto = 0
85class AES_CBC_Decrypt:
86 """Decrypt API"""
87 def __init__(self, key, iv):
88 if _have_crypto == 2:
89 self.decrypt = AES.new(key, AES.MODE_CBC, iv).decrypt
90 else:
91 ciph = Cipher(algorithms.AES(key), modes.CBC(iv), default_backend())
92 self.decrypt = ciph.decryptor().update
95__version__ = "4.5"
97# export only interesting items
98__all__ = ["get_rar_version", "is_rarfile", "is_rarfile_sfx", "RarInfo", "RarFile", "RarExtFile"]
100##
101## Module configuration. Can be tuned after importing.
102##
104#: executable for unrar tool
105UNRAR_TOOL = "unrar"
107#: executable for unar tool
108UNAR_TOOL = "unar"
110#: executable for bsdtar tool
111BSDTAR_TOOL = "bsdtar"
113#: executable for p7zip/7z tool
114SEVENZIP_TOOL = "7z"
116#: executable for alternative 7z tool
117SEVENZIP2_TOOL = "7zz"
119#: default fallback charset
120DEFAULT_CHARSET = "windows-1252"
122#: list of encodings to try, with fallback to DEFAULT_CHARSET if none succeed
123TRY_ENCODINGS = ("utf8", "utf-16le")
125#: whether to speed up decompression by using tmp archive
126USE_EXTRACT_HACK = 1
128#: limit the filesize for tmp archive usage
129HACK_SIZE_LIMIT = 20 * 1024 * 1024
131#: set specific directory for mkstemp() used by hack dir usage
132HACK_TMP_DIR = None
134#: Separator for path name components. Always "/".
135PATH_SEP = "/"
137##
138## rar constants
139##
141# block types
142RAR_BLOCK_MARK = 0x72 # r
143RAR_BLOCK_MAIN = 0x73 # s
144RAR_BLOCK_FILE = 0x74 # t
145RAR_BLOCK_OLD_COMMENT = 0x75 # u
146RAR_BLOCK_OLD_EXTRA = 0x76 # v
147RAR_BLOCK_OLD_SUB = 0x77 # w
148RAR_BLOCK_OLD_RECOVERY = 0x78 # x
149RAR_BLOCK_OLD_AUTH = 0x79 # y
150RAR_BLOCK_SUB = 0x7a # z
151RAR_BLOCK_ENDARC = 0x7b # {
153# flags for RAR_BLOCK_MAIN
154RAR_MAIN_VOLUME = 0x0001
155RAR_MAIN_COMMENT = 0x0002
156RAR_MAIN_LOCK = 0x0004
157RAR_MAIN_SOLID = 0x0008
158RAR_MAIN_NEWNUMBERING = 0x0010
159RAR_MAIN_AUTH = 0x0020
160RAR_MAIN_RECOVERY = 0x0040
161RAR_MAIN_PASSWORD = 0x0080
162RAR_MAIN_FIRSTVOLUME = 0x0100
163RAR_MAIN_ENCRYPTVER = 0x0200
165# flags for RAR_BLOCK_FILE
166RAR_FILE_SPLIT_BEFORE = 0x0001
167RAR_FILE_SPLIT_AFTER = 0x0002
168RAR_FILE_PASSWORD = 0x0004
169RAR_FILE_COMMENT = 0x0008
170RAR_FILE_SOLID = 0x0010
171RAR_FILE_DICTMASK = 0x00e0
172RAR_FILE_DICT64 = 0x0000
173RAR_FILE_DICT128 = 0x0020
174RAR_FILE_DICT256 = 0x0040
175RAR_FILE_DICT512 = 0x0060
176RAR_FILE_DICT1024 = 0x0080
177RAR_FILE_DICT2048 = 0x00a0
178RAR_FILE_DICT4096 = 0x00c0
179RAR_FILE_DIRECTORY = 0x00e0
180RAR_FILE_LARGE = 0x0100
181RAR_FILE_UNICODE = 0x0200
182RAR_FILE_SALT = 0x0400
183RAR_FILE_VERSION = 0x0800
184RAR_FILE_EXTTIME = 0x1000
185RAR_FILE_EXTFLAGS = 0x2000
187# flags for RAR_BLOCK_ENDARC
188RAR_ENDARC_NEXT_VOLUME = 0x0001
189RAR_ENDARC_DATACRC = 0x0002
190RAR_ENDARC_REVSPACE = 0x0004
191RAR_ENDARC_VOLNR = 0x0008
193# flags common to all blocks
194RAR_SKIP_IF_UNKNOWN = 0x4000
195RAR_LONG_BLOCK = 0x8000
197# Subtypes for RAR_BLOCK_OLD_SUB
198RAR_OLD_SUB_OS2 = 0x100
199RAR_OLD_SUB_UNIX = 0x101
200RAR_OLD_SUB_MAC = 0x102
201RAR_OLD_SUB_BEOS = 0x103
202RAR_OLD_SUB_NT = 0x104
203RAR_OLD_SUB_STREAM = 0x105
205# Host OS types
206RAR_OS_MSDOS = 0 #: MSDOS (only in RAR3)
207RAR_OS_OS2 = 1 #: OS2 (only in RAR3)
208RAR_OS_WIN32 = 2 #: Windows
209RAR_OS_UNIX = 3 #: UNIX
210RAR_OS_MACOS = 4 #: MacOS (only in RAR3)
211RAR_OS_BEOS = 5 #: BeOS (only in RAR3)
213# Compression methods - "0".."5"
214RAR_M0 = 0x30 #: No compression.
215RAR_M1 = 0x31 #: Compression level `-m1` - Fastest compression.
216RAR_M2 = 0x32 #: Compression level `-m2`.
217RAR_M3 = 0x33 #: Compression level `-m3`.
218RAR_M4 = 0x34 #: Compression level `-m4`.
219RAR_M5 = 0x35 #: Compression level `-m5` - Maximum compression.
221RAR_MAX_PASSWORD = 127 #: Max number of utf-16 chars in passwords.
222RAR_MAX_KDF_SHIFT = 24 #: Max power-of-2 for KDF count
223RAR_MAX_COMMENT = 256 * 1024 #: Max supported comment size
225#
226# RAR5 constants
227#
229RAR5_BLOCK_MAIN = 1
230RAR5_BLOCK_FILE = 2
231RAR5_BLOCK_SERVICE = 3
232RAR5_BLOCK_ENCRYPTION = 4
233RAR5_BLOCK_ENDARC = 5
235RAR5_BLOCK_FLAG_EXTRA_DATA = 0x01
236RAR5_BLOCK_FLAG_DATA_AREA = 0x02
237RAR5_BLOCK_FLAG_SKIP_IF_UNKNOWN = 0x04
238RAR5_BLOCK_FLAG_SPLIT_BEFORE = 0x08
239RAR5_BLOCK_FLAG_SPLIT_AFTER = 0x10
240RAR5_BLOCK_FLAG_DEPENDS_PREV = 0x20
241RAR5_BLOCK_FLAG_KEEP_WITH_PARENT = 0x40
243RAR5_MAIN_FLAG_ISVOL = 0x01
244RAR5_MAIN_FLAG_HAS_VOLNR = 0x02
245RAR5_MAIN_FLAG_SOLID = 0x04
246RAR5_MAIN_FLAG_RECOVERY = 0x08
247RAR5_MAIN_FLAG_LOCKED = 0x10
249RAR5_FILE_FLAG_ISDIR = 0x01
250RAR5_FILE_FLAG_HAS_MTIME = 0x02
251RAR5_FILE_FLAG_HAS_CRC32 = 0x04
252RAR5_FILE_FLAG_UNKNOWN_SIZE = 0x08
254RAR5_COMPR_SOLID = 0x40
256RAR5_ENC_FLAG_HAS_CHECKVAL = 0x01
258RAR5_ENDARC_FLAG_NEXT_VOL = 0x01
260RAR5_XFILE_ENCRYPTION = 1
261RAR5_XFILE_HASH = 2
262RAR5_XFILE_TIME = 3
263RAR5_XFILE_VERSION = 4
264RAR5_XFILE_REDIR = 5
265RAR5_XFILE_OWNER = 6
266RAR5_XFILE_SERVICE = 7
268RAR5_XTIME_UNIXTIME = 0x01
269RAR5_XTIME_HAS_MTIME = 0x02
270RAR5_XTIME_HAS_CTIME = 0x04
271RAR5_XTIME_HAS_ATIME = 0x08
272RAR5_XTIME_UNIXTIME_NS = 0x10
274RAR5_XENC_CIPHER_AES256 = 0
276RAR5_XENC_CHECKVAL = 0x01
277RAR5_XENC_TWEAKED = 0x02
279RAR5_XHASH_BLAKE2SP = 0
281RAR5_XREDIR_UNIX_SYMLINK = 1
282RAR5_XREDIR_WINDOWS_SYMLINK = 2
283RAR5_XREDIR_WINDOWS_JUNCTION = 3
284RAR5_XREDIR_HARD_LINK = 4
285RAR5_XREDIR_FILE_COPY = 5
287RAR5_XREDIR_ISDIR = 0x01
289RAR5_XOWNER_UNAME = 0x01
290RAR5_XOWNER_GNAME = 0x02
291RAR5_XOWNER_UID = 0x04
292RAR5_XOWNER_GID = 0x08
294RAR5_OS_WINDOWS = 0
295RAR5_OS_UNIX = 1
297DOS_MODE_ARCHIVE = 0x20
298DOS_MODE_DIR = 0x10
299DOS_MODE_SYSTEM = 0x04
300DOS_MODE_HIDDEN = 0x02
301DOS_MODE_READONLY = 0x01
303RAR5_PW_CHECK_SIZE = 8
304RAR5_PW_SUM_SIZE = 4
306##
307## internal constants
308##
310RAR_ID = b"Rar!\x1a\x07\x00"
311RAR5_ID = b"Rar!\x1a\x07\x01\x00"
313WIN32 = sys.platform == "win32"
314BSIZE = 512 * 1024 if WIN32 else 64 * 1024
316SFX_MAX_SIZE = 2 * 1024 * 1024
317RAR_V3 = 3
318RAR_V5 = 5
320_BAD_CHARS = r"""\x00-\x1F<>|"?*"""
321RC_BAD_CHARS_UNIX = re.compile(r"[%s]" % _BAD_CHARS)
322RC_BAD_CHARS_WIN32 = re.compile(r"[%s:^\\]" % _BAD_CHARS)
324FORCE_TOOL = False
327def _find_sfx_header(xfile):
328 sig = RAR_ID[:-1]
329 buf = io.BytesIO()
330 steps = (64, SFX_MAX_SIZE)
332 with XFile(xfile) as fd:
333 for step in steps:
334 data = fd.read(step)
335 if not data:
336 break
337 buf.write(data)
338 curdata = buf.getvalue()
339 findpos = 0
340 while True:
341 pos = curdata.find(sig, findpos)
342 if pos < 0:
343 break
344 if curdata[pos:pos + len(RAR_ID)] == RAR_ID:
345 return RAR_V3, pos
346 if curdata[pos:pos + len(RAR5_ID)] == RAR5_ID:
347 return RAR_V5, pos
348 findpos = pos + len(sig)
349 fd.restore_pos()
350 return 0, 0
353##
354## Public interface
355##
358def get_rar_version(xfile):
359 """Check quickly whether file is rar archive.
360 """
361 with XFile(xfile) as fd:
362 buf = fd.read(len(RAR5_ID))
363 fd.restore_pos()
364 if buf.startswith(RAR_ID):
365 return RAR_V3
366 elif buf.startswith(RAR5_ID):
367 return RAR_V5
368 return 0
371def is_rarfile(xfile):
372 """Check quickly whether file is rar archive.
373 """
374 try:
375 return get_rar_version(xfile) > 0
376 except OSError:
377 # File not found or not accessible, ignore
378 return False
381def is_rarfile_sfx(xfile):
382 """Check whether file is rar archive with support for SFX.
384 It will read 2M from file.
385 """
386 return _find_sfx_header(xfile)[0] > 0
389class Error(Exception):
390 """Base class for rarfile errors."""
393class BadRarFile(Error):
394 """Incorrect data in archive."""
397class NotRarFile(Error):
398 """The file is not RAR archive."""
401class BadRarName(Error):
402 """Cannot guess multipart name components."""
405class NoRarEntry(Error):
406 """File not found in RAR"""
409class PasswordRequired(Error):
410 """File requires password"""
413class BadSymLinkError(Error):
414 """Invalid symbolic link"""
417class NeedFirstVolume(Error):
418 """Need to start from first volume.
420 Attributes:
422 current_volume
423 Volume number of current file or None if not known
424 """
425 def __init__(self, msg, volume):
426 super().__init__(msg)
427 self.current_volume = volume
430class NoCrypto(Error):
431 """Cannot parse encrypted headers - no crypto available."""
434class RarExecError(Error):
435 """Problem reported by unrar/rar."""
438class RarWarning(RarExecError):
439 """Non-fatal error"""
442class RarFatalError(RarExecError):
443 """Fatal error"""
446class RarCRCError(RarExecError):
447 """CRC error during unpacking"""
450class RarLockedArchiveError(RarExecError):
451 """Must not modify locked archive"""
454class RarWriteError(RarExecError):
455 """Write error"""
458class RarOpenError(RarExecError):
459 """Open error"""
462class RarUserError(RarExecError):
463 """User error"""
466class RarMemoryError(RarExecError):
467 """Memory error"""
470class RarCreateError(RarExecError):
471 """Create error"""
474class RarNoFilesError(RarExecError):
475 """No files that match pattern were found"""
478class RarUserBreak(RarExecError):
479 """User stop"""
482class RarWrongPassword(RarExecError):
483 """Incorrect password"""
486class RarUnknownError(RarExecError):
487 """Unknown exit code"""
490class RarSignalExit(RarExecError):
491 """Unrar exited with signal"""
494class RarCannotExec(RarExecError):
495 """Executable not found."""
498class UnsupportedWarning(UserWarning):
499 """Archive uses feature that are unsupported by rarfile.
501 .. versionadded:: 4.0
502 """
505class RarInfo:
506 r"""An entry in rar archive.
508 Timestamps as :class:`~datetime.datetime` are without timezone in RAR3,
509 with UTC timezone in RAR5 archives.
511 Attributes:
513 filename
514 File name with relative path.
515 Path separator is "/". Always unicode string.
517 date_time
518 File modification timestamp. As tuple of (year, month, day, hour, minute, second).
519 RAR5 allows archives where it is missing, it's None then.
521 comment
522 Optional file comment field. Unicode string. (RAR3-only)
524 file_size
525 Uncompressed size.
527 compress_size
528 Compressed size.
530 compress_type
531 Compression method: one of :data:`RAR_M0` .. :data:`RAR_M5` constants.
533 extract_version
534 Minimal Rar version needed for decompressing. As (major*10 + minor),
535 so 2.9 is 29.
537 RAR3: 10, 20, 29
539 RAR5 does not have such field in archive, it's simply set to 50.
541 host_os
542 Host OS type, one of RAR_OS_* constants.
544 RAR3: :data:`RAR_OS_WIN32`, :data:`RAR_OS_UNIX`, :data:`RAR_OS_MSDOS`,
545 :data:`RAR_OS_OS2`, :data:`RAR_OS_BEOS`.
547 RAR5: :data:`RAR_OS_WIN32`, :data:`RAR_OS_UNIX`.
549 mode
550 File attributes. May be either dos-style or unix-style, depending on host_os.
552 mtime
553 File modification time. Same value as :attr:`date_time`
554 but as :class:`~datetime.datetime` object with extended precision.
556 ctime
557 Optional time field: creation time. As :class:`~datetime.datetime` object.
559 atime
560 Optional time field: last access time. As :class:`~datetime.datetime` object.
562 arctime
563 Optional time field: archival time. As :class:`~datetime.datetime` object.
564 (RAR3-only)
566 CRC
567 CRC-32 of uncompressed file, unsigned int.
569 RAR5: may be None.
571 blake2sp_hash
572 Blake2SP hash over decompressed data. (RAR5-only)
574 volume
575 Volume nr, starting from 0.
577 volume_file
578 Volume file name, where file starts.
580 file_redir
581 If not None, file is link of some sort. Contains tuple of (type, flags, target).
582 (RAR5-only)
584 Type is one of constants:
586 :data:`RAR5_XREDIR_UNIX_SYMLINK`
587 Unix symlink.
588 :data:`RAR5_XREDIR_WINDOWS_SYMLINK`
589 Windows symlink.
590 :data:`RAR5_XREDIR_WINDOWS_JUNCTION`
591 Windows junction.
592 :data:`RAR5_XREDIR_HARD_LINK`
593 Hard link to target.
594 :data:`RAR5_XREDIR_FILE_COPY`
595 Current file is copy of another archive entry.
597 Flags may contain bits:
599 :data:`RAR5_XREDIR_ISDIR`
600 Symlink points to directory.
601 """
603 # zipfile-compatible fields
604 filename = None
605 file_size = None
606 compress_size = None
607 date_time = None
608 CRC = None
609 volume = None
610 orig_filename = None
612 # optional extended time fields, datetime() objects.
613 mtime = None
614 ctime = None
615 atime = None
617 extract_version = None
618 mode = None
619 host_os = None
620 compress_type = None
622 # rar3-only fields
623 comment = None
624 arctime = None
626 # rar5-only fields
627 blake2sp_hash = None
628 file_redir = None
630 # internal fields
631 flags = 0
632 type = None
634 # zipfile compat
635 def is_dir(self):
636 """Returns True if entry is a directory.
638 .. versionadded:: 4.0
639 """
640 return False
642 def is_symlink(self):
643 """Returns True if entry is a symlink.
645 .. versionadded:: 4.0
646 """
647 return False
649 def is_file(self):
650 """Returns True if entry is a normal file.
652 .. versionadded:: 4.0
653 """
654 return False
656 def needs_password(self):
657 """Returns True if data is stored password-protected.
658 """
659 if self.type == RAR_BLOCK_FILE:
660 return (self.flags & RAR_FILE_PASSWORD) > 0
661 return False
663 def isdir(self):
664 """Returns True if entry is a directory.
666 .. deprecated:: 4.0
667 """
668 return self.is_dir()
671class RarFile:
672 """Parse RAR structure, provide access to files in archive.
674 Parameters:
676 file
677 archive file name or file-like object.
678 mode
679 only "r" is supported.
680 charset
681 fallback charset to use, if filenames are not already Unicode-enabled.
682 info_callback
683 debug callback, gets to see all archive entries.
684 crc_check
685 set to False to disable CRC checks
686 errors
687 Either "stop" to quietly stop parsing on errors,
688 or "strict" to raise errors. Default is "stop".
689 part_only
690 If True, read only single file and allow it to be middle-part
691 of multi-volume archive.
693 .. versionadded:: 4.0
694 """
696 #: File name, if available. Unicode string or None.
697 filename = None
699 #: Archive comment. Unicode string or None.
700 comment = None
702 def __init__(self, file, mode="r", charset=None, info_callback=None,
703 crc_check=True, errors="stop", part_only=False):
704 if is_filelike(file):
705 self.filename = getattr(file, "name", None)
706 else:
707 if isinstance(file, Path):
708 file = str(file)
709 self.filename = file
710 self._rarfile = file
712 self._charset = charset or DEFAULT_CHARSET
713 self._info_callback = info_callback
714 self._crc_check = crc_check
715 self._part_only = part_only
716 self._password = None
717 self._file_parser = None
719 if errors == "stop":
720 self._strict = False
721 elif errors == "strict":
722 self._strict = True
723 else:
724 raise ValueError("Invalid value for errors= parameter.")
726 if mode != "r":
727 raise NotImplementedError("RarFile supports only mode=r")
729 self._parse()
731 def __enter__(self):
732 """Open context."""
733 return self
735 def __exit__(self, typ, value, traceback):
736 """Exit context."""
737 self.close()
739 def __iter__(self):
740 """Iterate over members."""
741 return iter(self.infolist())
743 def setpassword(self, pwd):
744 """Sets the password to use when extracting.
745 """
746 self._password = pwd
747 if self._file_parser:
748 if self._file_parser.has_header_encryption():
749 self._file_parser = None
750 if not self._file_parser:
751 self._parse()
752 else:
753 self._file_parser.setpassword(self._password)
755 def needs_password(self):
756 """Returns True if any archive entries require password for extraction.
757 """
758 return self._file_parser.needs_password()
760 def is_solid(self):
761 """Returns True if archive uses solid compression.
763 .. versionadded:: 4.2
764 """
765 return self._file_parser.is_solid()
767 def namelist(self):
768 """Return list of filenames in archive.
769 """
770 return [f.filename for f in self.infolist()]
772 def infolist(self):
773 """Return RarInfo objects for all files/directories in archive.
774 """
775 return self._file_parser.infolist()
777 def volumelist(self):
778 """Returns filenames of archive volumes.
780 In case of single-volume archive, the list contains
781 just the name of main archive file.
782 """
783 return self._file_parser.volumelist()
785 def getinfo(self, name):
786 """Return RarInfo for file.
787 """
788 return self._file_parser.getinfo(name)
790 def getinfo_orig(self, name):
791 """Return RarInfo for file source.
793 RAR5: if name is hard-linked or copied file,
794 returns original entry with original filename.
796 .. versionadded:: 4.1
797 """
798 return self._file_parser.getinfo_orig(name)
800 def open(self, name, mode="r", pwd=None):
801 """Returns file-like object (:class:`RarExtFile`) from where the data can be read.
803 The object implements :class:`io.RawIOBase` interface, so it can
804 be further wrapped with :class:`io.BufferedReader`
805 and :class:`io.TextIOWrapper`.
807 On older Python where io module is not available, it implements
808 only .read(), .seek(), .tell() and .close() methods.
810 The object is seekable, although the seeking is fast only on
811 uncompressed files, on compressed files the seeking is implemented
812 by reading ahead and/or restarting the decompression.
814 Parameters:
816 name
817 file name or RarInfo instance.
818 mode
819 must be "r"
820 pwd
821 password to use for extracting.
822 """
824 if mode != "r":
825 raise NotImplementedError("RarFile.open() supports only mode=r")
827 # entry lookup
828 inf = self.getinfo(name)
829 if inf.is_dir():
830 raise io.UnsupportedOperation("Directory does not have any data: " + inf.filename)
832 # check password
833 if inf.needs_password():
834 pwd = pwd or self._password
835 if pwd is None:
836 raise PasswordRequired("File %s requires password" % inf.filename)
837 else:
838 pwd = None
840 return self._file_parser.open(inf, pwd)
842 def read(self, name, pwd=None):
843 """Return uncompressed data for archive entry.
845 For longer files using :meth:`~RarFile.open` may be better idea.
847 Parameters:
849 name
850 filename or RarInfo instance
851 pwd
852 password to use for extracting.
853 """
855 with self.open(name, "r", pwd) as f:
856 return f.read()
858 def close(self):
859 """Release open resources."""
860 pass
862 def printdir(self, file=None):
863 """Print archive file list to stdout or given file.
864 """
865 if file is None:
866 file = sys.stdout
867 for f in self.infolist():
868 print(f.filename, file=file)
870 def extract(self, member, path=None, pwd=None):
871 """Extract single file into current directory.
873 Parameters:
875 member
876 filename or :class:`RarInfo` instance
877 path
878 optional destination path
879 pwd
880 optional password to use
881 """
882 inf = self.getinfo(member)
883 return self._extract_one(inf, path, pwd, True)
885 def extractall(self, path=None, members=None, pwd=None):
886 """Extract all files into current directory.
888 Parameters:
890 path
891 optional destination path
892 members
893 optional filename or :class:`RarInfo` instance list to extract
894 pwd
895 optional password to use
896 """
897 if members is None:
898 members = self.namelist()
900 done = set()
901 dirs = []
902 for m in members:
903 inf = self.getinfo(m)
904 dst = self._extract_one(inf, path, pwd, not inf.is_dir())
905 if inf.is_dir():
906 if dst not in done:
907 dirs.append((dst, inf))
908 done.add(dst)
909 if dirs:
910 dirs.sort(reverse=True)
911 for dst, inf in dirs:
912 self._set_attrs(inf, dst)
914 def testrar(self, pwd=None):
915 """Read all files and test CRC.
916 """
917 for member in self.infolist():
918 if member.is_file():
919 with self.open(member, 'r', pwd) as f:
920 empty_read(f, member.file_size, BSIZE)
922 def strerror(self):
923 """Return error string if parsing failed or None if no problems.
924 """
925 if not self._file_parser:
926 return "Not a RAR file"
927 return self._file_parser.strerror()
929 ##
930 ## private methods
931 ##
933 def _parse(self):
934 """Run parser for file type
935 """
936 ver, sfx_ofs = _find_sfx_header(self._rarfile)
937 if ver == RAR_V3:
938 p3 = RAR3Parser(self._rarfile, self._password, self._crc_check,
939 self._charset, self._strict, self._info_callback,
940 sfx_ofs, self._part_only)
941 self._file_parser = p3 # noqa
942 elif ver == RAR_V5:
943 p5 = RAR5Parser(self._rarfile, self._password, self._crc_check,
944 self._charset, self._strict, self._info_callback,
945 sfx_ofs, self._part_only)
946 self._file_parser = p5 # noqa
947 else:
948 raise NotRarFile("Not a RAR file")
950 self._file_parser.parse()
951 self.comment = self._file_parser.comment
953 def _extract_one(self, info, path, pwd, set_attrs):
954 fname = sanitize_filename(
955 info.filename, os.path.sep, WIN32
956 )
958 if path is None:
959 path = os.getcwd()
960 else:
961 path = os.fspath(path)
962 dstfn = os.path.join(path, fname)
964 # Reject members whose destination escapes `path` once symlinks
965 # already created on disk are resolved. Without this, a symlink
966 # member can point outside `path` and a later file/dir member
967 # named through it will be written outside the extraction root.
968 real_path = os.path.realpath(path)
969 real_dst = os.path.realpath(dstfn)
970 if real_dst != real_path and not real_dst.startswith(real_path + os.sep):
971 raise BadRarFile(
972 "Refusing to extract entry that escapes destination: %r" % info.filename
973 )
975 dirname = os.path.dirname(dstfn)
976 if dirname and dirname != ".":
977 os.makedirs(dirname, exist_ok=True)
979 if info.is_file():
980 return self._make_file(info, dstfn, pwd, set_attrs)
981 if info.is_dir():
982 return self._make_dir(info, dstfn, pwd, set_attrs)
983 if info.is_symlink():
984 return self._make_symlink(info, dstfn, pwd, set_attrs, path)
985 return None
987 def _create_helper(self, name, flags, info):
988 return os.open(name, flags)
990 def _make_file(self, info, dstfn, pwd, set_attrs):
991 def helper(name, flags):
992 return self._create_helper(name, flags, info)
993 with self.open(info, "r", pwd) as src:
994 with open(dstfn, "wb", opener=helper) as dst:
995 shutil.copyfileobj(src, dst)
996 if set_attrs:
997 self._set_attrs(info, dstfn)
998 return dstfn
1000 def _make_dir(self, info, dstfn, pwd, set_attrs):
1001 os.makedirs(dstfn, exist_ok=True)
1002 if set_attrs:
1003 self._set_attrs(info, dstfn)
1004 return dstfn
1006 def _make_symlink(self, info, dstfn, pwd, set_attrs, top):
1007 target_is_directory = False
1008 if info.host_os == RAR_OS_UNIX:
1009 link_name = self.read(info, pwd).decode("utf8", "replace")
1010 target_is_directory = (info.flags & RAR_FILE_DIRECTORY) == RAR_FILE_DIRECTORY
1011 elif info.file_redir:
1012 redir_type, redir_flags, link_name = info.file_redir
1013 if redir_type == RAR5_XREDIR_WINDOWS_JUNCTION:
1014 warnings.warn(f"Windows junction not supported - {info.filename}", UnsupportedWarning)
1015 return None
1016 target_is_directory = (redir_type & RAR5_XREDIR_ISDIR) > 0
1017 else:
1018 warnings.warn(f"Unsupported link type - {info.filename}", UnsupportedWarning)
1019 return None
1021 # disallow abs paths
1022 target = os.path.normpath(link_name)
1023 if os.path.isabs(target) or os.path.splitdrive(target)[0]:
1024 raise BadSymLinkError('Absolute links not allowed')
1026 # disallow ../ traversal
1027 dest_abs = os.path.realpath(top)
1028 target_base = os.path.dirname(dstfn)
1029 target_abs = os.path.realpath(os.path.join(target_base, target))
1030 if os.path.commonpath([target_abs, dest_abs]) != dest_abs:
1031 raise BadSymLinkError('Link to outside not allowed')
1033 os.symlink(link_name, dstfn, target_is_directory=target_is_directory)
1034 return dstfn
1036 def _set_attrs(self, info, dstfn):
1037 if info.host_os == RAR_OS_UNIX:
1038 os.chmod(dstfn, info.mode & 0o777)
1039 elif info.host_os in (RAR_OS_WIN32, RAR_OS_MSDOS):
1040 # only keep R/O attr, except for dirs on win32
1041 if info.mode & DOS_MODE_READONLY and (info.is_file() or not WIN32):
1042 st = os.stat(dstfn)
1043 new_mode = st.st_mode & ~0o222
1044 os.chmod(dstfn, new_mode)
1046 if info.mtime:
1047 mtime_ns = to_nsecs(info.mtime)
1048 atime_ns = to_nsecs(info.atime) if info.atime else mtime_ns
1049 os.utime(dstfn, ns=(atime_ns, mtime_ns))
1052#
1053# File format parsing
1054#
1056class CommonParser:
1057 """Shared parser parts."""
1058 _main = None
1059 _hdrenc_main = None
1060 _needs_password = False
1061 _fd = None
1062 _expect_sig = None
1063 _parse_error = None
1064 _password = None
1065 comment = None
1067 def __init__(self, rarfile, password, crc_check, charset, strict,
1068 info_cb, sfx_offset, part_only):
1069 self._rarfile = rarfile
1070 self._password = password
1071 self._crc_check = crc_check
1072 self._charset = charset
1073 self._strict = strict
1074 self._info_callback = info_cb
1075 self._info_list = []
1076 self._info_map = {}
1077 self._vol_list = []
1078 self._sfx_offset = sfx_offset
1079 self._part_only = part_only
1081 def is_solid(self):
1082 """Returns True if archive uses solid compression.
1083 """
1084 if self._main:
1085 if self._main.flags & RAR_MAIN_SOLID:
1086 return True
1087 return False
1089 def has_header_encryption(self):
1090 """Returns True if headers are encrypted
1091 """
1092 if self._hdrenc_main:
1093 return True
1094 if self._main:
1095 if self._main.flags & RAR_MAIN_PASSWORD:
1096 return True
1097 return False
1099 def setpassword(self, pwd):
1100 """Set cached password."""
1101 self._password = pwd
1103 def volumelist(self):
1104 """Volume files"""
1105 return self._vol_list
1107 def needs_password(self):
1108 """Is password required"""
1109 return self._needs_password
1111 def strerror(self):
1112 """Last error"""
1113 return self._parse_error
1115 def infolist(self):
1116 """List of RarInfo records.
1117 """
1118 return self._info_list
1120 def getinfo(self, member):
1121 """Return RarInfo for filename
1122 """
1123 if isinstance(member, RarInfo):
1124 fname = member.filename
1125 elif isinstance(member, Path):
1126 fname = str(member)
1127 else:
1128 fname = member
1130 if fname.endswith("/"):
1131 fname = fname.rstrip("/")
1133 try:
1134 return self._info_map[fname]
1135 except KeyError:
1136 raise NoRarEntry("No such file: %s" % fname) from None
1138 def getinfo_orig(self, member):
1139 inf = self.getinfo(member)
1140 if inf.file_redir:
1141 redir_type, redir_flags, redir_name = inf.file_redir
1142 # cannot leave to unrar as it expects copied file to exist
1143 if redir_type in (RAR5_XREDIR_FILE_COPY, RAR5_XREDIR_HARD_LINK):
1144 inf = self.getinfo(redir_name)
1145 return inf
1147 def parse(self):
1148 """Process file."""
1149 self._fd = None
1150 try:
1151 self._parse_real()
1152 finally:
1153 if self._fd:
1154 self._fd.close()
1155 self._fd = None
1157 def _parse_real(self):
1158 """Actually read file.
1159 """
1160 fd = XFile(self._rarfile)
1161 self._fd = fd
1162 fd.seek(self._sfx_offset, 0)
1163 sig = fd.read(len(self._expect_sig))
1164 if sig != self._expect_sig:
1165 raise NotRarFile("Not a Rar archive")
1167 volume = 0 # first vol (.rar) is 0
1168 more_vols = False
1169 endarc = False
1170 volfile = self._rarfile
1171 self._vol_list = [self._rarfile]
1172 raise_need_first_vol = False
1173 while True:
1174 if endarc:
1175 h = None # don"t read past ENDARC
1176 else:
1177 h = self._parse_header(fd)
1178 if not h:
1179 if raise_need_first_vol:
1180 # did not find ENDARC with VOLNR
1181 raise NeedFirstVolume("Need to start from first volume", None)
1182 if more_vols and not self._part_only:
1183 volume += 1
1184 fd.close()
1185 try:
1186 volfile = self._next_volname(volfile)
1187 fd = XFile(volfile)
1188 except IOError:
1189 self._set_error("Cannot open next volume: %s", volfile)
1190 break
1191 self._fd = fd
1192 sig = fd.read(len(self._expect_sig))
1193 if sig != self._expect_sig:
1194 self._set_error("Invalid volume sig: %s", volfile)
1195 break
1196 more_vols = False
1197 endarc = False
1198 self._vol_list.append(volfile)
1199 self._main = None
1200 self._hdrenc_main = None
1201 continue
1202 break
1203 h.volume = volume
1204 h.volume_file = volfile
1206 if h.type == RAR_BLOCK_MAIN and not self._main:
1207 self._main = h
1208 if volume == 0 and (h.flags & RAR_MAIN_NEWNUMBERING) and not self._part_only:
1209 # RAR 2.x does not set FIRSTVOLUME,
1210 # so check it only if NEWNUMBERING is used
1211 if (h.flags & RAR_MAIN_FIRSTVOLUME) == 0:
1212 if getattr(h, "main_volume_number", None) is not None:
1213 # rar5 may have more info
1214 raise NeedFirstVolume(
1215 "Need to start from first volume (current: %r)"
1216 % (h.main_volume_number,),
1217 h.main_volume_number
1218 )
1219 # delay raise until we have volnr from ENDARC
1220 raise_need_first_vol = True
1221 if h.flags & RAR_MAIN_PASSWORD:
1222 self._needs_password = True
1223 if not self._password:
1224 break
1225 elif h.type == RAR_BLOCK_ENDARC:
1226 # use flag, but also allow RAR 2.x logic below to trigger
1227 if h.flags & RAR_ENDARC_NEXT_VOLUME:
1228 more_vols = True
1229 endarc = True
1230 if raise_need_first_vol and (h.flags & RAR_ENDARC_VOLNR) > 0:
1231 raise NeedFirstVolume(
1232 "Need to start from first volume (current: %r)"
1233 % (h.endarc_volnr,),
1234 h.endarc_volnr
1235 )
1236 elif h.type == RAR_BLOCK_FILE:
1237 # RAR 2.x does not write RAR_BLOCK_ENDARC
1238 if h.flags & RAR_FILE_SPLIT_AFTER:
1239 more_vols = True
1240 # RAR 2.x does not set RAR_MAIN_FIRSTVOLUME
1241 if volume == 0 and h.flags & RAR_FILE_SPLIT_BEFORE:
1242 if not self._part_only:
1243 raise_need_first_vol = True
1245 if h.needs_password():
1246 self._needs_password = True
1248 # store it
1249 self.process_entry(fd, h)
1251 if self._info_callback:
1252 self._info_callback(h)
1254 # go to next header
1255 if h.add_size > 0:
1256 fd.seek(h.data_offset + h.add_size, 0)
1258 def process_entry(self, fd, item):
1259 """Examine item, add into lookup cache."""
1260 raise NotImplementedError()
1262 def _decrypt_header(self, fd):
1263 raise NotImplementedError("_decrypt_header")
1265 def _parse_block_header(self, fd):
1266 raise NotImplementedError("_parse_block_header")
1268 def _open_hack(self, inf, pwd):
1269 raise NotImplementedError("_open_hack")
1271 def _parse_header(self, fd):
1272 """Read single header
1273 """
1274 try:
1275 # handle encrypted headers
1276 if (self._main and self._main.flags & RAR_MAIN_PASSWORD) or self._hdrenc_main:
1277 if not self._password:
1278 return None
1279 fd = self._decrypt_header(fd)
1281 # now read actual header
1282 return self._parse_block_header(fd)
1283 except struct.error:
1284 self._set_error("Broken header in RAR file")
1285 return None
1287 def _next_volname(self, volfile):
1288 """Given current vol name, construct next one
1289 """
1290 if is_filelike(volfile):
1291 raise IOError("Working on single FD")
1292 if self._main.flags & RAR_MAIN_NEWNUMBERING:
1293 return _next_newvol(volfile)
1294 return _next_oldvol(volfile)
1296 def _set_error(self, msg, *args):
1297 if args:
1298 msg = msg % args
1299 self._parse_error = msg
1300 if self._strict:
1301 raise BadRarFile(msg)
1303 def open(self, inf, pwd):
1304 """Return stream object for file data."""
1306 if inf.file_redir:
1307 redir_type, redir_flags, redir_name = inf.file_redir
1308 # cannot leave to unrar as it expects copied file to exist
1309 if redir_type in (RAR5_XREDIR_FILE_COPY, RAR5_XREDIR_HARD_LINK):
1310 inf = self.getinfo(redir_name)
1311 if not inf:
1312 raise BadRarFile("cannot find copied file")
1313 elif redir_type in (
1314 RAR5_XREDIR_UNIX_SYMLINK, RAR5_XREDIR_WINDOWS_SYMLINK,
1315 RAR5_XREDIR_WINDOWS_JUNCTION,
1316 ):
1317 return io.BytesIO(redir_name.encode("utf8"))
1318 if inf.flags & RAR_FILE_SPLIT_BEFORE:
1319 raise NeedFirstVolume("Partial file, please start from first volume: " + inf.filename, None)
1321 # is temp write usable?
1322 use_hack = 1
1323 if not self._main:
1324 use_hack = 0
1325 elif self._main._must_disable_hack():
1326 use_hack = 0
1327 elif inf._must_disable_hack():
1328 use_hack = 0
1329 elif is_filelike(self._rarfile):
1330 pass
1331 elif inf.file_size > HACK_SIZE_LIMIT:
1332 use_hack = 0
1333 elif not USE_EXTRACT_HACK:
1334 use_hack = 0
1336 # now extract
1337 if inf.compress_type == RAR_M0 and (inf.flags & RAR_FILE_PASSWORD) == 0 and inf.file_redir is None:
1338 return self._open_clear(inf)
1339 elif use_hack:
1340 return self._open_hack(inf, pwd)
1341 elif is_filelike(self._rarfile):
1342 return self._open_unrar_membuf(self._rarfile, inf, pwd)
1343 else:
1344 return self._open_unrar(self._rarfile, inf, pwd)
1346 def _open_clear(self, inf):
1347 if FORCE_TOOL:
1348 return self._open_unrar(self._rarfile, inf)
1349 return DirectReader(self, inf)
1351 def _open_hack_core(self, inf, pwd, prefix, suffix):
1353 size = inf.compress_size + inf.header_size
1354 rf = XFile(inf.volume_file, 0)
1355 rf.seek(inf.header_offset)
1357 tmpfd, tmpname = mkstemp(suffix=".rar", dir=HACK_TMP_DIR)
1358 tmpf = os.fdopen(tmpfd, "wb")
1360 try:
1361 tmpf.write(prefix)
1362 while size > 0:
1363 if size > BSIZE:
1364 buf = rf.read(BSIZE)
1365 else:
1366 buf = rf.read(size)
1367 if not buf:
1368 raise BadRarFile("read failed: " + inf.filename)
1369 tmpf.write(buf)
1370 size -= len(buf)
1371 tmpf.write(suffix)
1372 tmpf.close()
1373 rf.close()
1374 except BaseException:
1375 rf.close()
1376 tmpf.close()
1377 os.unlink(tmpname)
1378 raise
1380 return self._open_unrar(tmpname, inf, pwd, tmpname)
1382 def _open_unrar_membuf(self, memfile, inf, pwd):
1383 """Write in-memory archive to temp file, needed for solid archives.
1384 """
1385 tmpname = membuf_tempfile(memfile)
1386 return self._open_unrar(tmpname, inf, pwd, tmpname, force_file=True)
1388 def _open_unrar(self, rarfile, inf, pwd=None, tmpfile=None, force_file=False):
1389 """Extract using unrar
1390 """
1391 setup = tool_setup()
1393 # not giving filename avoids encoding related problems
1394 fn = None
1395 if not tmpfile or force_file:
1396 fn = inf.filename.replace("/", os.path.sep)
1398 # read from unrar pipe
1399 cmd = setup.open_cmdline(pwd, rarfile, fn)
1400 return PipeReader(self, inf, cmd, tmpfile)
1403#
1404# RAR3 format
1405#
1407class Rar3Info(RarInfo):
1408 """RAR3 specific fields."""
1409 extract_version = 15
1410 salt = None
1411 add_size = 0
1412 header_crc = None
1413 header_size = None
1414 header_offset = None
1415 data_offset = None
1416 _md_class = None
1417 _md_expect = None
1418 _name_size = None
1420 # make sure some rar5 fields are always present
1421 file_redir = None
1422 blake2sp_hash = None
1424 endarc_datacrc = None
1425 endarc_volnr = None
1427 old_sub_type = None
1429 def _must_disable_hack(self):
1430 if self.type == RAR_BLOCK_FILE:
1431 if self.flags & RAR_FILE_PASSWORD:
1432 return True
1433 elif self.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER):
1434 return True
1435 elif self.type == RAR_BLOCK_MAIN:
1436 if self.flags & (RAR_MAIN_SOLID | RAR_MAIN_PASSWORD):
1437 return True
1438 return False
1440 def is_dir(self):
1441 """Returns True if entry is a directory."""
1442 if self.type == RAR_BLOCK_FILE and not self.is_symlink():
1443 return (self.flags & RAR_FILE_DIRECTORY) == RAR_FILE_DIRECTORY
1444 return False
1446 def is_symlink(self):
1447 """Returns True if entry is a symlink."""
1448 return (
1449 self.type == RAR_BLOCK_FILE and
1450 self.host_os == RAR_OS_UNIX and
1451 self.mode & 0xF000 == 0xA000
1452 )
1454 def is_file(self):
1455 """Returns True if entry is a normal file."""
1456 return (
1457 self.type == RAR_BLOCK_FILE and
1458 not (self.is_dir() or self.is_symlink())
1459 )
1462class RAR3Parser(CommonParser):
1463 """Parse RAR3 file format.
1464 """
1465 _expect_sig = RAR_ID
1466 _last_aes_key = (None, None, None) # (salt, key, iv)
1468 def _decrypt_header(self, fd):
1469 if not _have_crypto:
1470 raise NoCrypto("Cannot parse encrypted headers - no crypto")
1471 salt = fd.read(8)
1472 if self._last_aes_key[0] == salt:
1473 key, iv = self._last_aes_key[1:]
1474 else:
1475 key, iv = rar3_s2k(self._password, salt)
1476 self._last_aes_key = (salt, key, iv)
1477 return HeaderDecrypt(fd, key, iv)
1479 def _parse_block_header(self, fd):
1480 """Parse common block header
1481 """
1482 h = Rar3Info()
1483 h.header_offset = fd.tell()
1485 # read and parse base header
1486 buf = fd.read(S_BLK_HDR.size)
1487 if not buf:
1488 return None
1489 if len(buf) < S_BLK_HDR.size:
1490 self._set_error("Unexpected EOF when reading header")
1491 return None
1492 t = S_BLK_HDR.unpack_from(buf)
1493 h.header_crc, h.type, h.flags, h.header_size = t
1495 # read full header
1496 if h.header_size > S_BLK_HDR.size:
1497 hdata = buf + fd.read(h.header_size - S_BLK_HDR.size)
1498 else:
1499 hdata = buf
1500 h.data_offset = fd.tell()
1502 # unexpected EOF?
1503 if len(hdata) != h.header_size:
1504 self._set_error("Unexpected EOF when reading header")
1505 return None
1507 pos = S_BLK_HDR.size
1509 # block has data assiciated with it?
1510 if h.flags & RAR_LONG_BLOCK:
1511 h.add_size, pos = load_le32(hdata, pos)
1512 else:
1513 h.add_size = 0
1515 # parse interesting ones, decide header boundaries for crc
1516 if h.type == RAR_BLOCK_MARK:
1517 return h
1518 elif h.type == RAR_BLOCK_MAIN:
1519 pos += 6
1520 if h.flags & RAR_MAIN_ENCRYPTVER:
1521 pos += 1
1522 crc_pos = pos
1523 if h.flags & RAR_MAIN_COMMENT:
1524 self._parse_subblocks(h, hdata, pos)
1525 elif h.type == RAR_BLOCK_FILE:
1526 pos = self._parse_file_header(h, hdata, pos - 4)
1527 crc_pos = pos
1528 if h.flags & RAR_FILE_COMMENT:
1529 pos = self._parse_subblocks(h, hdata, pos)
1530 elif h.type == RAR_BLOCK_SUB:
1531 pos = self._parse_file_header(h, hdata, pos - 4)
1532 crc_pos = h.header_size
1533 elif h.type == RAR_BLOCK_OLD_AUTH:
1534 pos += 8
1535 crc_pos = pos
1536 elif h.type == RAR_BLOCK_OLD_EXTRA:
1537 pos += 7
1538 crc_pos = pos
1539 elif h.type == RAR_BLOCK_OLD_SUB:
1540 pos = self._parse_old_subblock(h, hdata, pos)
1542 # these types do not have their own data CRC,
1543 # so data was included in header CRC.
1544 if h.old_sub_type in (RAR_OLD_SUB_UNIX, RAR_OLD_SUB_MAC):
1545 # skip CRC check, it requires to read data part
1546 return h
1548 crc_pos = h.header_size
1549 elif h.type == RAR_BLOCK_ENDARC:
1550 if h.flags & RAR_ENDARC_DATACRC:
1551 h.endarc_datacrc, pos = load_le32(hdata, pos)
1552 if h.flags & RAR_ENDARC_VOLNR:
1553 h.endarc_volnr = S_SHORT.unpack_from(hdata, pos)[0]
1554 pos += 2
1555 crc_pos = h.header_size
1556 else:
1557 crc_pos = h.header_size
1559 # calculate crc
1560 crcdat = hdata[2:crc_pos]
1561 calc_crc = crc32(crcdat) & 0xFFFF
1563 # return good header
1564 if h.header_crc == calc_crc:
1565 return h
1567 # header parsing failed.
1568 self._set_error("Header CRC error (%02x): exp=%x got=%x (xlen = %d)",
1569 h.type, h.header_crc, calc_crc, len(crcdat))
1571 # instead panicing, send eof
1572 return None
1574 def _parse_file_header(self, h, hdata, pos):
1575 """Read file-specific header
1576 """
1577 fld = S_FILE_HDR.unpack_from(hdata, pos)
1578 pos += S_FILE_HDR.size
1580 h.compress_size = fld[0]
1581 h.file_size = fld[1]
1582 h.host_os = fld[2]
1583 h.CRC = fld[3]
1584 h.date_time = parse_dos_time(fld[4])
1585 h.mtime = to_datetime(h.date_time)
1586 h.extract_version = fld[5]
1587 h.compress_type = fld[6]
1588 h._name_size = name_size = fld[7]
1589 h.mode = fld[8]
1591 h._md_class = CRC32Context
1592 h._md_expect = h.CRC
1594 if h.flags & RAR_FILE_LARGE:
1595 h1, pos = load_le32(hdata, pos)
1596 h2, pos = load_le32(hdata, pos)
1597 h.compress_size |= h1 << 32
1598 h.file_size |= h2 << 32
1599 h.add_size = h.compress_size
1601 name, pos = load_bytes(hdata, name_size, pos)
1602 if h.flags & RAR_FILE_UNICODE and b"\0" in name:
1603 # stored in custom encoding
1604 nul = name.find(b"\0")
1605 h.orig_filename = name[:nul]
1606 u = UnicodeFilename(h.orig_filename, name[nul + 1:])
1607 h.filename = u.decode()
1609 # if parsing failed fall back to simple name
1610 if u.failed:
1611 h.filename = self._decode(h.orig_filename)
1612 elif h.flags & RAR_FILE_UNICODE:
1613 # stored in UTF8
1614 h.orig_filename = name
1615 h.filename = name.decode("utf8", "replace")
1616 else:
1617 nul = name.find(b"\0")
1618 if nul >= 0:
1619 name = name[:nul]
1620 # stored in random encoding
1621 h.orig_filename = name
1622 h.filename = self._decode(name)
1624 # change separator, set dir suffix
1625 h.filename = h.filename.replace("\\", "/").rstrip("/")
1626 if h.is_dir():
1627 h.filename = h.filename + "/"
1629 if h.flags & RAR_FILE_SALT:
1630 h.salt, pos = load_bytes(hdata, 8, pos)
1631 else:
1632 h.salt = None
1634 # optional extended time stamps
1635 if h.flags & RAR_FILE_EXTTIME:
1636 pos = _parse_ext_time(h, hdata, pos)
1637 else:
1638 h.mtime = h.atime = h.ctime = h.arctime = None
1640 return pos
1642 def _parse_old_subblock(self, h, hdata, pos):
1643 """Parse RAR2 subblock
1644 """
1645 h.old_sub_type, _reserved = S_OLD_SUBBLOCK_HDR.unpack_from(hdata, pos)
1646 return pos
1648 def _parse_subblocks(self, h, hdata, pos):
1649 """Find old-style comment subblock
1650 """
1651 while pos < len(hdata):
1652 # ordinary block header
1653 t = S_BLK_HDR.unpack_from(hdata, pos)
1654 ___scrc, stype, sflags, slen = t
1655 pos_next = pos + slen
1656 pos += S_BLK_HDR.size
1658 # corrupt header
1659 if pos_next < pos:
1660 break
1662 # followed by block-specific header
1663 if stype == RAR_BLOCK_OLD_COMMENT and pos + S_COMMENT_HDR.size <= pos_next:
1664 declen, ver, meth, crc = S_COMMENT_HDR.unpack_from(hdata, pos)
1665 if declen > RAR_MAX_COMMENT:
1666 pos = pos_next
1667 continue
1668 pos += S_COMMENT_HDR.size
1669 data = hdata[pos: pos_next]
1670 cmt = rar3_decompress(ver, meth, data, declen, sflags,
1671 crc, self._password)
1672 if not self._crc_check or (crc32(cmt) & 0xFFFF == crc):
1673 h.comment = self._decode_comment(cmt)
1675 pos = pos_next
1676 return pos
1678 def _read_comment_v3(self, inf, pwd=None):
1680 if inf.compress_size > RAR_MAX_COMMENT:
1681 return None
1682 if inf.file_size > RAR_MAX_COMMENT:
1683 return None
1685 # read data
1686 with XFile(inf.volume_file) as rf:
1687 rf.seek(inf.data_offset)
1688 data = rf.read(inf.compress_size)
1690 # decompress
1691 cmt = rar3_decompress(inf.extract_version, inf.compress_type, data,
1692 inf.file_size, inf.flags, inf.CRC, pwd, inf.salt)
1694 # check crc
1695 if self._crc_check:
1696 crc = crc32(cmt)
1697 if crc != inf.CRC:
1698 return None
1700 return self._decode_comment(cmt)
1702 def _decode(self, val):
1703 for c in TRY_ENCODINGS:
1704 try:
1705 return val.decode(c)
1706 except UnicodeError:
1707 pass
1708 return val.decode(self._charset, "replace")
1710 def _decode_comment(self, val):
1711 return self._decode(val)
1713 def process_entry(self, fd, item):
1714 if item.type == RAR_BLOCK_FILE:
1715 # use only first part
1716 if item.flags & RAR_FILE_VERSION:
1717 pass # skip old versions
1718 elif (item.flags & RAR_FILE_SPLIT_BEFORE) == 0:
1719 self._info_map[item.filename.rstrip("/")] = item
1720 self._info_list.append(item)
1721 elif len(self._info_list) > 0:
1722 # final crc is in last block
1723 old = self._info_list[-1]
1724 old.CRC = item.CRC
1725 old._md_expect = item._md_expect
1726 old.compress_size += item.compress_size
1728 # parse new-style comment
1729 if item.type == RAR_BLOCK_SUB and item.filename == "CMT":
1730 if item.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER):
1731 pass
1732 elif item.flags & RAR_FILE_SOLID:
1733 # file comment
1734 cmt = self._read_comment_v3(item, self._password)
1735 if len(self._info_list) > 0:
1736 old = self._info_list[-1]
1737 old.comment = cmt
1738 else:
1739 # archive comment
1740 cmt = self._read_comment_v3(item, self._password)
1741 self.comment = cmt
1743 if item.type == RAR_BLOCK_MAIN:
1744 if item.flags & RAR_MAIN_COMMENT:
1745 self.comment = item.comment
1746 if item.flags & RAR_MAIN_PASSWORD:
1747 self._needs_password = True
1749 # put file compressed data into temporary .rar archive, and run
1750 # unrar on that, thus avoiding unrar going over whole archive
1751 def _open_hack(self, inf, pwd):
1752 # create main header: crc, type, flags, size, res1, res2
1753 prefix = RAR_ID + S_BLK_HDR.pack(0x90CF, 0x73, 0, 13) + b"\0" * (2 + 4)
1754 return self._open_hack_core(inf, pwd, prefix, b"")
1757#
1758# RAR5 format
1759#
1761class Rar5Info(RarInfo):
1762 """Shared fields for RAR5 records.
1763 """
1764 extract_version = 50
1765 header_crc = None
1766 header_size = None
1767 header_offset = None
1768 data_offset = None
1770 # type=all
1771 block_type = None
1772 block_flags = None
1773 add_size = 0
1774 block_extra_size = 0
1776 # type=MAIN
1777 volume_number = None
1778 _md_class = None
1779 _md_expect = None
1781 def _must_disable_hack(self):
1782 return False
1785class Rar5BaseFile(Rar5Info):
1786 """Shared sturct for file & service record.
1787 """
1788 type = -1
1789 file_flags = None
1790 file_encryption = (0, 0, 0, b"", b"", b"")
1791 file_compress_flags = None
1792 file_redir = None
1793 file_owner = None
1794 file_version = None
1795 blake2sp_hash = None
1797 def _must_disable_hack(self):
1798 if self.flags & RAR_FILE_PASSWORD:
1799 return True
1800 if self.block_flags & (RAR5_BLOCK_FLAG_SPLIT_BEFORE | RAR5_BLOCK_FLAG_SPLIT_AFTER):
1801 return True
1802 if self.file_compress_flags & RAR5_COMPR_SOLID:
1803 return True
1804 if self.file_redir:
1805 return True
1806 return False
1809class Rar5FileInfo(Rar5BaseFile):
1810 """RAR5 file record.
1811 """
1812 type = RAR_BLOCK_FILE
1814 def is_symlink(self):
1815 """Returns True if entry is a symlink."""
1816 # pylint: disable=unsubscriptable-object
1817 return (
1818 self.file_redir is not None and
1819 self.file_redir[0] in (
1820 RAR5_XREDIR_UNIX_SYMLINK,
1821 RAR5_XREDIR_WINDOWS_SYMLINK,
1822 RAR5_XREDIR_WINDOWS_JUNCTION,
1823 )
1824 )
1826 def is_file(self):
1827 """Returns True if entry is a normal file."""
1828 return not (self.is_dir() or self.is_symlink())
1830 def is_dir(self):
1831 """Returns True if entry is a directory."""
1832 if not self.file_redir:
1833 if self.file_flags & RAR5_FILE_FLAG_ISDIR:
1834 return True
1835 return False
1838class Rar5ServiceInfo(Rar5BaseFile):
1839 """RAR5 service record.
1840 """
1841 type = RAR_BLOCK_SUB
1844class Rar5MainInfo(Rar5Info):
1845 """RAR5 archive main record.
1846 """
1847 type = RAR_BLOCK_MAIN
1848 main_flags = None
1849 main_volume_number = None
1851 def _must_disable_hack(self):
1852 if self.main_flags & RAR5_MAIN_FLAG_SOLID:
1853 return True
1854 return False
1857class Rar5EncryptionInfo(Rar5Info):
1858 """RAR5 archive header encryption record.
1859 """
1860 type = RAR5_BLOCK_ENCRYPTION
1861 encryption_algo = None
1862 encryption_flags = None
1863 encryption_kdf_count = None
1864 encryption_salt = None
1865 encryption_check_value = None
1867 def needs_password(self):
1868 return True
1871class Rar5EndArcInfo(Rar5Info):
1872 """RAR5 end of archive record.
1873 """
1874 type = RAR_BLOCK_ENDARC
1875 endarc_flags = None
1878class RAR5Parser(CommonParser):
1879 """Parse RAR5 format.
1880 """
1881 _expect_sig = RAR5_ID
1882 _hdrenc_main = None
1884 # AES encrypted headers
1885 _last_aes256_key = (-1, None, None) # (kdf_count, salt, key)
1887 def _get_utf8_password(self):
1888 pwd = self._password
1889 if isinstance(pwd, str):
1890 return pwd.encode("utf8")
1891 return pwd
1893 def _gen_key(self, kdf_count, salt):
1894 if self._last_aes256_key[:2] == (kdf_count, salt):
1895 return self._last_aes256_key[2]
1896 if kdf_count > RAR_MAX_KDF_SHIFT:
1897 raise BadRarFile("Too large kdf_count")
1898 pwd = self._get_utf8_password()
1899 key = rar5_s2k(pwd, salt, 1 << kdf_count)
1900 self._last_aes256_key = (kdf_count, salt, key)
1901 return key
1903 def _decrypt_header(self, fd):
1904 if not _have_crypto:
1905 raise NoCrypto("Cannot parse encrypted headers - no crypto")
1906 h = self._hdrenc_main
1907 key = self._gen_key(h.encryption_kdf_count, h.encryption_salt)
1908 iv = fd.read(16)
1909 return HeaderDecrypt(fd, key, iv)
1911 def _parse_block_header(self, fd):
1912 """Parse common block header
1913 """
1914 header_offset = fd.tell()
1916 preload = 4 + 1
1917 start_bytes = fd.read(preload)
1918 if len(start_bytes) < preload:
1919 self._set_error("Unexpected EOF when reading header")
1920 return None
1921 while start_bytes[-1] & 0x80:
1922 b = fd.read(1)
1923 if not b:
1924 self._set_error("Unexpected EOF when reading header")
1925 return None
1926 start_bytes += b
1927 header_crc, pos = load_le32(start_bytes, 0)
1928 hdrlen, pos = load_vint(start_bytes, pos)
1929 if hdrlen > 2 * 1024 * 1024:
1930 return None
1931 header_size = pos + hdrlen
1933 # read full header, check for EOF
1934 hdata = start_bytes + fd.read(header_size - len(start_bytes))
1935 if len(hdata) != header_size:
1936 self._set_error("Unexpected EOF when reading header")
1937 return None
1938 data_offset = fd.tell()
1940 calc_crc = crc32(memoryview(hdata)[4:])
1941 if header_crc != calc_crc:
1942 # header parsing failed.
1943 self._set_error("Header CRC error: exp=%x got=%x (xlen = %d)",
1944 header_crc, calc_crc, len(hdata))
1945 return None
1947 block_type, pos = load_vint(hdata, pos)
1949 if block_type == RAR5_BLOCK_MAIN:
1950 h, pos = self._parse_block_common(Rar5MainInfo(), hdata)
1951 h = self._parse_main_block(h, hdata, pos)
1952 elif block_type == RAR5_BLOCK_FILE:
1953 h, pos = self._parse_block_common(Rar5FileInfo(), hdata)
1954 h = self._parse_file_block(h, hdata, pos)
1955 elif block_type == RAR5_BLOCK_SERVICE:
1956 h, pos = self._parse_block_common(Rar5ServiceInfo(), hdata)
1957 h = self._parse_file_block(h, hdata, pos)
1958 elif block_type == RAR5_BLOCK_ENCRYPTION:
1959 h, pos = self._parse_block_common(Rar5EncryptionInfo(), hdata)
1960 h = self._parse_encryption_block(h, hdata, pos)
1961 elif block_type == RAR5_BLOCK_ENDARC:
1962 h, pos = self._parse_block_common(Rar5EndArcInfo(), hdata)
1963 h = self._parse_endarc_block(h, hdata, pos)
1964 else:
1965 h = None
1966 if h:
1967 h.header_offset = header_offset
1968 h.data_offset = data_offset
1969 return h
1971 def _parse_block_common(self, h, hdata):
1972 h.header_crc, pos = load_le32(hdata, 0)
1973 hdrlen, pos = load_vint(hdata, pos)
1974 h.header_size = hdrlen + pos
1975 h.block_type, pos = load_vint(hdata, pos)
1976 h.block_flags, pos = load_vint(hdata, pos)
1978 if h.block_flags & RAR5_BLOCK_FLAG_EXTRA_DATA:
1979 h.block_extra_size, pos = load_vint(hdata, pos)
1980 if h.block_flags & RAR5_BLOCK_FLAG_DATA_AREA:
1981 h.add_size, pos = load_vint(hdata, pos)
1983 h.compress_size = h.add_size
1985 if h.block_flags & RAR5_BLOCK_FLAG_SKIP_IF_UNKNOWN:
1986 h.flags |= RAR_SKIP_IF_UNKNOWN
1987 if h.block_flags & RAR5_BLOCK_FLAG_DATA_AREA:
1988 h.flags |= RAR_LONG_BLOCK
1989 return h, pos
1991 def _parse_main_block(self, h, hdata, pos):
1992 h.main_flags, pos = load_vint(hdata, pos)
1993 if h.main_flags & RAR5_MAIN_FLAG_HAS_VOLNR:
1994 h.main_volume_number, pos = load_vint(hdata, pos)
1996 h.flags |= RAR_MAIN_NEWNUMBERING
1997 if h.main_flags & RAR5_MAIN_FLAG_SOLID:
1998 h.flags |= RAR_MAIN_SOLID
1999 if h.main_flags & RAR5_MAIN_FLAG_ISVOL:
2000 h.flags |= RAR_MAIN_VOLUME
2001 if h.main_flags & RAR5_MAIN_FLAG_RECOVERY:
2002 h.flags |= RAR_MAIN_RECOVERY
2003 if self._hdrenc_main:
2004 h.flags |= RAR_MAIN_PASSWORD
2005 if h.main_flags & RAR5_MAIN_FLAG_HAS_VOLNR == 0:
2006 h.flags |= RAR_MAIN_FIRSTVOLUME
2008 return h
2010 def _parse_file_block(self, h, hdata, pos):
2011 h.file_flags, pos = load_vint(hdata, pos)
2012 h.file_size, pos = load_vint(hdata, pos)
2013 h.mode, pos = load_vint(hdata, pos)
2015 if h.file_flags & RAR5_FILE_FLAG_HAS_MTIME:
2016 h.mtime, pos = load_unixtime(hdata, pos)
2017 h.date_time = h.mtime.timetuple()[:6]
2018 if h.file_flags & RAR5_FILE_FLAG_HAS_CRC32:
2019 h.CRC, pos = load_le32(hdata, pos)
2020 h._md_class = CRC32Context
2021 h._md_expect = h.CRC
2023 h.file_compress_flags, pos = load_vint(hdata, pos)
2024 h.file_host_os, pos = load_vint(hdata, pos)
2026 name, pos = load_vstr(hdata, pos)
2027 nul = name.find(b"\0")
2028 if nul >= 0:
2029 name = name[:nul]
2030 h.orig_filename = name
2031 h.filename = h.orig_filename.decode("utf8", "replace").rstrip("/")
2033 # use compatible values
2034 if h.file_host_os == RAR5_OS_WINDOWS:
2035 h.host_os = RAR_OS_WIN32
2036 else:
2037 h.host_os = RAR_OS_UNIX
2038 h.compress_type = RAR_M0 + ((h.file_compress_flags >> 7) & 7)
2040 if h.block_extra_size:
2041 # allow 1 byte of garbage
2042 while pos < len(hdata) - 1:
2043 xsize, pos = load_vint(hdata, pos)
2044 xdata, pos = load_bytes(hdata, xsize, pos)
2045 self._process_file_extra(h, xdata)
2047 if h.block_flags & RAR5_BLOCK_FLAG_SPLIT_BEFORE:
2048 h.flags |= RAR_FILE_SPLIT_BEFORE
2049 if h.block_flags & RAR5_BLOCK_FLAG_SPLIT_AFTER:
2050 h.flags |= RAR_FILE_SPLIT_AFTER
2051 if h.file_flags & RAR5_FILE_FLAG_ISDIR:
2052 h.flags |= RAR_FILE_DIRECTORY
2053 if h.file_compress_flags & RAR5_COMPR_SOLID:
2054 h.flags |= RAR_FILE_SOLID
2056 if h.is_dir():
2057 h.filename = h.filename + "/"
2058 return h
2060 def _parse_endarc_block(self, h, hdata, pos):
2061 h.endarc_flags, pos = load_vint(hdata, pos)
2062 if h.endarc_flags & RAR5_ENDARC_FLAG_NEXT_VOL:
2063 h.flags |= RAR_ENDARC_NEXT_VOLUME
2064 return h
2066 def _check_password(self, check_value, kdf_count_shift, salt):
2067 if len(check_value) != RAR5_PW_CHECK_SIZE + RAR5_PW_SUM_SIZE:
2068 return
2069 if kdf_count_shift > RAR_MAX_KDF_SHIFT:
2070 raise BadRarFile("Too large kdf_count")
2072 hdr_check = check_value[:RAR5_PW_CHECK_SIZE]
2073 hdr_sum = check_value[RAR5_PW_CHECK_SIZE:]
2074 sum_hash = sha256(hdr_check).digest()
2075 if sum_hash[:RAR5_PW_SUM_SIZE] != hdr_sum:
2076 return
2078 kdf_count = (1 << kdf_count_shift) + 32
2079 pwd = self._get_utf8_password()
2080 pwd_hash = rar5_s2k(pwd, salt, kdf_count)
2082 pwd_check = bytearray(RAR5_PW_CHECK_SIZE)
2083 len_mask = RAR5_PW_CHECK_SIZE - 1
2084 for i, v in enumerate(pwd_hash):
2085 pwd_check[i & len_mask] ^= v
2087 if pwd_check != hdr_check:
2088 raise RarWrongPassword()
2090 def _parse_encryption_block(self, h, hdata, pos):
2091 self._hdrenc_main = h
2092 self._needs_password = True
2093 h.encryption_algo, pos = load_vint(hdata, pos)
2094 h.encryption_flags, pos = load_vint(hdata, pos)
2095 h.encryption_kdf_count, pos = load_byte(hdata, pos)
2096 h.encryption_salt, pos = load_bytes(hdata, 16, pos)
2097 if h.encryption_flags & RAR5_ENC_FLAG_HAS_CHECKVAL:
2098 h.encryption_check_value, pos = load_bytes(hdata, 12, pos)
2099 if h.encryption_algo != RAR5_XENC_CIPHER_AES256:
2100 raise BadRarFile("Unsupported header encryption cipher")
2101 if h.encryption_check_value and self._password:
2102 self._check_password(h.encryption_check_value, h.encryption_kdf_count, h.encryption_salt)
2103 return h
2105 def _process_file_extra(self, h, xdata):
2106 xtype, pos = load_vint(xdata, 0)
2107 if xtype == RAR5_XFILE_TIME:
2108 self._parse_file_xtime(h, xdata, pos)
2109 elif xtype == RAR5_XFILE_ENCRYPTION:
2110 self._parse_file_encryption(h, xdata, pos)
2111 elif xtype == RAR5_XFILE_HASH:
2112 self._parse_file_hash(h, xdata, pos)
2113 elif xtype == RAR5_XFILE_VERSION:
2114 self._parse_file_version(h, xdata, pos)
2115 elif xtype == RAR5_XFILE_REDIR:
2116 self._parse_file_redir(h, xdata, pos)
2117 elif xtype == RAR5_XFILE_OWNER:
2118 self._parse_file_owner(h, xdata, pos)
2119 elif xtype == RAR5_XFILE_SERVICE:
2120 pass
2121 else:
2122 pass
2124 # extra block for file time record
2125 def _parse_file_xtime(self, h, xdata, pos):
2126 tflags, pos = load_vint(xdata, pos)
2128 ldr = load_windowstime
2129 if tflags & RAR5_XTIME_UNIXTIME:
2130 ldr = load_unixtime
2132 if tflags & RAR5_XTIME_HAS_MTIME:
2133 h.mtime, pos = ldr(xdata, pos)
2134 h.date_time = h.mtime.timetuple()[:6]
2135 if tflags & RAR5_XTIME_HAS_CTIME:
2136 h.ctime, pos = ldr(xdata, pos)
2137 if tflags & RAR5_XTIME_HAS_ATIME:
2138 h.atime, pos = ldr(xdata, pos)
2140 if tflags & RAR5_XTIME_UNIXTIME_NS:
2141 if tflags & RAR5_XTIME_HAS_MTIME:
2142 nsec, pos = load_le32(xdata, pos)
2143 h.mtime = to_nsdatetime(h.mtime, nsec)
2144 if tflags & RAR5_XTIME_HAS_CTIME:
2145 nsec, pos = load_le32(xdata, pos)
2146 h.ctime = to_nsdatetime(h.ctime, nsec)
2147 if tflags & RAR5_XTIME_HAS_ATIME:
2148 nsec, pos = load_le32(xdata, pos)
2149 h.atime = to_nsdatetime(h.atime, nsec)
2151 # just remember encryption info
2152 def _parse_file_encryption(self, h, xdata, pos):
2153 algo, pos = load_vint(xdata, pos)
2154 flags, pos = load_vint(xdata, pos)
2155 kdf_count, pos = load_byte(xdata, pos)
2156 salt, pos = load_bytes(xdata, 16, pos)
2157 iv, pos = load_bytes(xdata, 16, pos)
2158 checkval = None
2159 if flags & RAR5_XENC_CHECKVAL:
2160 checkval, pos = load_bytes(xdata, 12, pos)
2161 if flags & RAR5_XENC_TWEAKED:
2162 h._md_expect = None
2163 h._md_class = NoHashContext
2165 h.file_encryption = (algo, flags, kdf_count, salt, iv, checkval)
2166 h.flags |= RAR_FILE_PASSWORD
2168 def _parse_file_hash(self, h, xdata, pos):
2169 hash_type, pos = load_vint(xdata, pos)
2170 if hash_type == RAR5_XHASH_BLAKE2SP:
2171 h.blake2sp_hash, pos = load_bytes(xdata, 32, pos)
2172 if (h.file_encryption[1] & RAR5_XENC_TWEAKED) == 0:
2173 h._md_class = Blake2SP
2174 h._md_expect = h.blake2sp_hash
2176 def _parse_file_version(self, h, xdata, pos):
2177 flags, pos = load_vint(xdata, pos)
2178 version, pos = load_vint(xdata, pos)
2179 h.file_version = (flags, version)
2181 def _parse_file_redir(self, h, xdata, pos):
2182 redir_type, pos = load_vint(xdata, pos)
2183 redir_flags, pos = load_vint(xdata, pos)
2184 redir_name, pos = load_vstr(xdata, pos)
2185 redir_name = redir_name.decode("utf8", "replace")
2186 h.file_redir = (redir_type, redir_flags, redir_name)
2188 def _parse_file_owner(self, h, xdata, pos):
2189 user_name = group_name = user_id = group_id = None
2191 flags, pos = load_vint(xdata, pos)
2192 if flags & RAR5_XOWNER_UNAME:
2193 user_name, pos = load_vstr(xdata, pos)
2194 if flags & RAR5_XOWNER_GNAME:
2195 group_name, pos = load_vstr(xdata, pos)
2196 if flags & RAR5_XOWNER_UID:
2197 user_id, pos = load_vint(xdata, pos)
2198 if flags & RAR5_XOWNER_GID:
2199 group_id, pos = load_vint(xdata, pos)
2201 h.file_owner = (user_name, group_name, user_id, group_id)
2203 def process_entry(self, fd, item):
2204 if item.block_type == RAR5_BLOCK_FILE:
2205 if item.file_version:
2206 pass # skip old versions
2207 elif (item.block_flags & RAR5_BLOCK_FLAG_SPLIT_BEFORE) == 0:
2208 # use only first part
2209 self._info_map[item.filename.rstrip("/")] = item
2210 self._info_list.append(item)
2211 elif len(self._info_list) > 0:
2212 # final crc is in last block
2213 old = self._info_list[-1]
2214 old.CRC = item.CRC
2215 old._md_expect = item._md_expect
2216 old.blake2sp_hash = item.blake2sp_hash
2217 old.compress_size += item.compress_size
2218 elif item.block_type == RAR5_BLOCK_SERVICE:
2219 if item.filename == "CMT":
2220 self._load_comment(fd, item)
2222 def _load_comment(self, fd, item):
2223 if item.block_flags & (RAR5_BLOCK_FLAG_SPLIT_BEFORE | RAR5_BLOCK_FLAG_SPLIT_AFTER):
2224 return None
2225 if item.compress_type != RAR_M0:
2226 return None
2227 if item.compress_size > RAR_MAX_COMMENT:
2228 return None
2229 if item.file_size > RAR_MAX_COMMENT:
2230 return None
2232 if item.flags & RAR_FILE_PASSWORD:
2233 algo, ___flags, kdf_count, salt, iv, ___checkval = item.file_encryption
2234 if algo != RAR5_XENC_CIPHER_AES256:
2235 return None
2236 key = self._gen_key(kdf_count, salt)
2237 f = HeaderDecrypt(fd, key, iv)
2238 cmt = f.read(item.file_size)
2239 else:
2240 # archive comment
2241 with self._open_clear(item) as cmtstream:
2242 cmt = cmtstream.read()
2244 # rar bug? - appends zero to comment
2245 cmt = cmt.split(b"\0", 1)[0]
2246 self.comment = cmt.decode("utf8")
2247 return None
2249 def _open_hack(self, inf, pwd):
2250 # len, type, blk_flags, flags
2251 main_hdr = b"\x03\x01\x00\x00"
2252 endarc_hdr = b"\x03\x05\x00\x00"
2253 main_hdr = S_LONG.pack(crc32(main_hdr)) + main_hdr
2254 endarc_hdr = S_LONG.pack(crc32(endarc_hdr)) + endarc_hdr
2255 return self._open_hack_core(inf, pwd, RAR5_ID + main_hdr, endarc_hdr)
2258##
2259## Utility classes
2260##
2262class UnicodeFilename:
2263 """Handle RAR3 unicode filename decompression.
2264 """
2265 def __init__(self, name, encdata):
2266 self.std_name = bytearray(name)
2267 self.encdata = bytearray(encdata)
2268 self.pos = self.encpos = 0
2269 self.buf = bytearray()
2270 self.failed = 0
2272 def enc_byte(self):
2273 """Copy encoded byte."""
2274 try:
2275 c = self.encdata[self.encpos]
2276 self.encpos += 1
2277 return c
2278 except IndexError:
2279 self.failed = 1
2280 return 0
2282 def std_byte(self):
2283 """Copy byte from 8-bit representation."""
2284 try:
2285 return self.std_name[self.pos]
2286 except IndexError:
2287 self.failed = 1
2288 return ord("?")
2290 def put(self, lo, hi):
2291 """Copy 16-bit value to result."""
2292 self.buf.append(lo)
2293 self.buf.append(hi)
2294 self.pos += 1
2296 def decode(self):
2297 """Decompress compressed UTF16 value."""
2298 hi = self.enc_byte()
2299 flagbits = 0
2300 while self.encpos < len(self.encdata):
2301 if flagbits == 0:
2302 flags = self.enc_byte()
2303 flagbits = 8
2304 flagbits -= 2
2305 t = (flags >> flagbits) & 3
2306 if t == 0:
2307 self.put(self.enc_byte(), 0)
2308 elif t == 1:
2309 self.put(self.enc_byte(), hi)
2310 elif t == 2:
2311 self.put(self.enc_byte(), self.enc_byte())
2312 else:
2313 n = self.enc_byte()
2314 if n & 0x80:
2315 c = self.enc_byte()
2316 for _ in range((n & 0x7f) + 2):
2317 lo = (self.std_byte() + c) & 0xFF
2318 self.put(lo, hi)
2319 else:
2320 for _ in range(n + 2):
2321 self.put(self.std_byte(), 0)
2322 return self.buf.decode("utf-16le", "replace")
2325class RarExtFile(io.RawIOBase):
2326 """Base class for file-like object that :meth:`RarFile.open` returns.
2328 Provides public methods and common crc checking.
2330 Behaviour:
2331 - no short reads - .read() and .readinfo() read as much as requested.
2332 - no internal buffer, use io.BufferedReader for that.
2333 """
2334 name = None #: Filename of the archive entry
2335 mode = "rb"
2336 _parser = None
2337 _inf = None
2338 _fd = None
2339 _remain = 0
2340 _returncode = 0
2341 _md_context = None
2342 _seeking = False
2344 def _open_extfile(self, parser, inf):
2345 self.name = inf.filename
2346 self._parser = parser
2347 self._inf = inf
2349 if self._fd:
2350 self._fd.close()
2351 if self._seeking:
2352 md_class = NoHashContext
2353 else:
2354 md_class = self._inf._md_class or NoHashContext
2355 self._md_context = md_class()
2356 self._fd = None
2357 self._remain = self._inf.file_size
2359 def read(self, n=-1):
2360 """Read all or specified amount of data from archive entry."""
2362 # sanitize count
2363 if n is None or n < 0:
2364 n = self._remain
2365 elif n > self._remain:
2366 n = self._remain
2367 if n == 0:
2368 return b""
2370 buf = []
2371 orig = n
2372 while n > 0:
2373 # actual read
2374 data = self._read(n)
2375 if not data:
2376 break
2377 buf.append(data)
2378 self._md_context.update(data)
2379 self._remain -= len(data)
2380 n -= len(data)
2381 data = b"".join(buf)
2382 if n > 0:
2383 if self._returncode:
2384 check_returncode(self._returncode, "", tool_setup().get_errmap())
2385 raise BadRarFile("Failed the read enough data: req=%d got=%d" % (orig, len(data)))
2387 # done?
2388 if not data or self._remain == 0:
2389 # self.close()
2390 self._check()
2391 return data
2393 def _check(self):
2394 """Check final CRC."""
2395 final = self._md_context.digest()
2396 exp = self._inf._md_expect
2397 if exp is None:
2398 return
2399 if final is None:
2400 return
2401 if self._returncode:
2402 check_returncode(self._returncode, "", tool_setup().get_errmap())
2403 if self._remain != 0:
2404 raise BadRarFile("Failed the read enough data")
2405 if final != exp:
2406 raise BadRarFile("Corrupt file - CRC check failed: %s - exp=%r got=%r" % (
2407 self._inf.filename, exp, final))
2409 def _read(self, cnt):
2410 """Actual read that gets sanitized cnt."""
2411 raise NotImplementedError("_read")
2413 def close(self):
2414 """Close open resources."""
2416 super().close()
2418 if self._fd:
2419 self._fd.close()
2420 self._fd = None
2422 def __del__(self):
2423 """Hook delete to make sure tempfile is removed."""
2424 self.close()
2426 def readinto(self, buf):
2427 """Zero-copy read directly into buffer.
2429 Returns bytes read.
2430 """
2431 raise NotImplementedError("readinto")
2433 def tell(self):
2434 """Return current reading position in uncompressed data."""
2435 return self._inf.file_size - self._remain
2437 def seek(self, offset, whence=0):
2438 """Seek in data.
2440 On uncompressed files, the seeking works by actual
2441 seeks so it's fast. On compressed files its slow
2442 - forward seeking happens by reading ahead,
2443 backwards by re-opening and decompressing from the start.
2444 """
2446 # disable crc check when seeking
2447 if not self._seeking:
2448 self._md_context = NoHashContext()
2449 self._seeking = True
2451 fsize = self._inf.file_size
2452 cur_ofs = self.tell()
2454 if whence == 0: # seek from beginning of file
2455 new_ofs = offset
2456 elif whence == 1: # seek from current position
2457 new_ofs = cur_ofs + offset
2458 elif whence == 2: # seek from end of file
2459 new_ofs = fsize + offset
2460 else:
2461 raise ValueError("Invalid value for whence")
2463 # sanity check
2464 if new_ofs < 0:
2465 new_ofs = 0
2466 elif new_ofs > fsize:
2467 new_ofs = fsize
2469 # do the actual seek
2470 if new_ofs >= cur_ofs:
2471 self._skip(new_ofs - cur_ofs)
2472 else:
2473 # reopen and seek
2474 self._open_extfile(self._parser, self._inf)
2475 self._skip(new_ofs)
2476 return self.tell()
2478 def _skip(self, cnt):
2479 """Read and discard data"""
2480 empty_read(self, cnt, BSIZE)
2482 def readable(self):
2483 """Returns True"""
2484 return True
2486 def writable(self):
2487 """Returns False.
2489 Writing is not supported.
2490 """
2491 return False
2493 def seekable(self):
2494 """Returns True.
2496 Seeking is supported, although it's slow on compressed files.
2497 """
2498 return True
2500 def readall(self):
2501 """Read all remaining data"""
2502 # avoid RawIOBase default impl
2503 return self.read()
2506class PipeReader(RarExtFile):
2507 """Read data from pipe, handle tempfile cleanup."""
2509 def __init__(self, parser, inf, cmd, tempfile=None):
2510 super().__init__()
2511 self._cmd = cmd
2512 self._proc = None
2513 self._tempfile = tempfile
2514 self._open_extfile(parser, inf)
2516 def _close_proc(self):
2517 if not self._proc:
2518 return
2519 for f in (self._proc.stdout, self._proc.stderr, self._proc.stdin):
2520 if f:
2521 f.close()
2522 self._proc.wait()
2523 self._returncode = self._proc.returncode
2524 self._proc = None
2526 def _open_extfile(self, parser, inf):
2527 super()._open_extfile(parser, inf)
2529 # stop old process
2530 self._close_proc()
2532 # launch new process
2533 self._returncode = 0
2534 self._proc = custom_popen(self._cmd)
2535 self._fd = self._proc.stdout
2537 def _read(self, cnt):
2538 """Read from pipe."""
2540 # normal read is usually enough
2541 data = self._fd.read(cnt)
2542 if len(data) == cnt or not data:
2543 return data
2545 # short read, try looping
2546 buf = [data]
2547 cnt -= len(data)
2548 while cnt > 0:
2549 data = self._fd.read(cnt)
2550 if not data:
2551 break
2552 cnt -= len(data)
2553 buf.append(data)
2554 return b"".join(buf)
2556 def close(self):
2557 """Close open resources."""
2559 self._close_proc()
2560 super().close()
2562 if self._tempfile:
2563 try:
2564 os.unlink(self._tempfile)
2565 except OSError:
2566 pass
2567 self._tempfile = None
2569 def readinto(self, buf):
2570 """Zero-copy read directly into buffer."""
2571 cnt = len(buf)
2572 if cnt > self._remain:
2573 cnt = self._remain
2574 vbuf = memoryview(buf)
2575 res = got = 0
2576 while got < cnt:
2577 res = self._fd.readinto(vbuf[got: cnt])
2578 if not res:
2579 break
2580 self._md_context.update(vbuf[got: got + res])
2581 self._remain -= res
2582 got += res
2583 return got
2586class DirectReader(RarExtFile):
2587 """Read uncompressed data directly from archive.
2588 """
2589 _cur = None
2590 _cur_avail = None
2591 _volfile = None
2593 def __init__(self, parser, inf):
2594 super().__init__()
2595 self._open_extfile(parser, inf)
2597 def _open_extfile(self, parser, inf):
2598 super()._open_extfile(parser, inf)
2600 self._volfile = self._inf.volume_file
2601 self._fd = XFile(self._volfile, 0)
2602 self._fd.seek(self._inf.header_offset, 0)
2603 self._cur = self._parser._parse_header(self._fd)
2604 self._cur_avail = self._cur.add_size
2606 def _skip(self, cnt):
2607 """RAR Seek, skipping through rar files to get to correct position
2608 """
2610 while cnt > 0:
2611 # next vol needed?
2612 if self._cur_avail == 0:
2613 if not self._open_next():
2614 break
2616 # fd is in read pos, do the read
2617 if cnt > self._cur_avail:
2618 cnt -= self._cur_avail
2619 self._remain -= self._cur_avail
2620 self._cur_avail = 0
2621 else:
2622 self._fd.seek(cnt, 1)
2623 self._cur_avail -= cnt
2624 self._remain -= cnt
2625 cnt = 0
2627 def _read(self, cnt):
2628 """Read from potentially multi-volume archive."""
2630 pos = self._fd.tell()
2631 need = self._cur.data_offset + self._cur.add_size - self._cur_avail
2632 if pos != need:
2633 self._fd.seek(need, 0)
2635 buf = []
2636 while cnt > 0:
2637 # next vol needed?
2638 if self._cur_avail == 0:
2639 if not self._open_next():
2640 break
2642 # fd is in read pos, do the read
2643 if cnt > self._cur_avail:
2644 data = self._fd.read(self._cur_avail)
2645 else:
2646 data = self._fd.read(cnt)
2647 if not data:
2648 break
2650 # got some data
2651 cnt -= len(data)
2652 self._cur_avail -= len(data)
2653 buf.append(data)
2655 if len(buf) == 1:
2656 return buf[0]
2657 return b"".join(buf)
2659 def _open_next(self):
2660 """Proceed to next volume."""
2662 # is the file split over archives?
2663 if (self._cur.flags & RAR_FILE_SPLIT_AFTER) == 0:
2664 return False
2666 if self._fd:
2667 self._fd.close()
2668 self._fd = None
2670 # open next part
2671 self._volfile = self._parser._next_volname(self._volfile)
2672 fd = open(self._volfile, "rb", 0)
2673 self._fd = fd
2674 sig = fd.read(len(self._parser._expect_sig))
2675 if sig != self._parser._expect_sig:
2676 raise BadRarFile("Invalid signature")
2678 # loop until first file header
2679 while True:
2680 cur = self._parser._parse_header(fd)
2681 if not cur:
2682 raise BadRarFile("Unexpected EOF")
2683 if cur.type in (RAR_BLOCK_MARK, RAR_BLOCK_MAIN):
2684 if cur.add_size:
2685 fd.seek(cur.add_size, 1)
2686 continue
2687 if cur.orig_filename != self._inf.orig_filename:
2688 raise BadRarFile("Did not found file entry")
2689 self._cur = cur
2690 self._cur_avail = cur.add_size
2691 return True
2693 def readinto(self, buf):
2694 """Zero-copy read directly into buffer."""
2695 got = 0
2696 vbuf = memoryview(buf)
2697 while got < len(buf):
2698 # next vol needed?
2699 if self._cur_avail == 0:
2700 if not self._open_next():
2701 break
2703 # length for next read
2704 cnt = len(buf) - got
2705 if cnt > self._cur_avail:
2706 cnt = self._cur_avail
2708 # read into temp view
2709 res = self._fd.readinto(vbuf[got: got + cnt])
2710 if not res:
2711 break
2712 self._md_context.update(vbuf[got: got + res])
2713 self._cur_avail -= res
2714 self._remain -= res
2715 got += res
2716 return got
2719class HeaderDecrypt:
2720 """File-like object that decrypts from another file"""
2721 def __init__(self, f, key, iv):
2722 self.f = f
2723 self.ciph = AES_CBC_Decrypt(key, iv)
2724 self.buf = b""
2726 def tell(self):
2727 """Current file pos - works only on block boundaries."""
2728 return self.f.tell()
2730 def read(self, cnt=None):
2731 """Read and decrypt."""
2732 if cnt > 8 * 1024:
2733 raise BadRarFile("Bad count to header decrypt - wrong password?")
2735 # consume old data
2736 if cnt <= len(self.buf):
2737 res = self.buf[:cnt]
2738 self.buf = self.buf[cnt:]
2739 return res
2740 res = self.buf
2741 self.buf = b""
2742 cnt -= len(res)
2744 # decrypt new data
2745 blklen = 16
2746 while cnt > 0:
2747 enc = self.f.read(blklen)
2748 if len(enc) < blklen:
2749 break
2750 dec = self.ciph.decrypt(enc)
2751 if cnt >= len(dec):
2752 res += dec
2753 cnt -= len(dec)
2754 else:
2755 res += dec[:cnt]
2756 self.buf = dec[cnt:]
2757 cnt = 0
2759 return res
2762class XFile:
2763 """Input may be filename or file object.
2764 """
2765 __slots__ = ("_fd", "_need_close", "_initial_pos")
2767 def __init__(self, xfile, bufsize=1024):
2768 if is_filelike(xfile):
2769 self._initial_pos = xfile.tell()
2770 self._need_close = False
2771 self._fd = xfile
2772 self._fd.seek(0)
2773 else:
2774 self._initial_pos = None
2775 self._need_close = True
2776 self._fd = open(xfile, "rb", bufsize)
2778 def restore_pos(self):
2779 if self._initial_pos is None:
2780 return
2781 try:
2782 self._fd.seek(self._initial_pos)
2783 except:
2784 pass
2786 def read(self, n=None):
2787 """Read from file."""
2788 return self._fd.read(n)
2790 def tell(self):
2791 """Return file pos."""
2792 return self._fd.tell()
2794 def seek(self, ofs, whence=0):
2795 """Move file pos."""
2796 return self._fd.seek(ofs, whence)
2798 def readinto(self, buf):
2799 """Read into buffer."""
2800 return self._fd.readinto(buf)
2802 def close(self):
2803 """Close file object."""
2804 if self._need_close:
2805 self._fd.close()
2807 def __enter__(self):
2808 return self
2810 def __exit__(self, typ, val, tb):
2811 self.close()
2814class NoHashContext:
2815 """No-op hash function."""
2816 def __init__(self, data=None):
2817 """Initialize"""
2818 def update(self, data):
2819 """Update data"""
2820 def digest(self):
2821 """Final hash"""
2822 def hexdigest(self):
2823 """Hexadecimal digest."""
2826class CRC32Context:
2827 """Hash context that uses CRC32."""
2828 __slots__ = ["_crc"]
2830 def __init__(self, data=None):
2831 self._crc = 0
2832 if data:
2833 self.update(data)
2835 def update(self, data):
2836 """Process data."""
2837 self._crc = crc32(data, self._crc)
2839 def digest(self):
2840 """Final hash."""
2841 return self._crc
2843 def hexdigest(self):
2844 """Hexadecimal digest."""
2845 return "%08x" % self.digest()
2848class Blake2SP:
2849 """Blake2sp hash context.
2850 """
2851 __slots__ = ["_thread", "_buf", "_cur", "_digest"]
2852 digest_size = 32
2853 block_size = 64
2854 parallelism = 8
2856 def __init__(self, data=None):
2857 self._buf = b""
2858 self._cur = 0
2859 self._digest = None
2860 self._thread = []
2862 for i in range(self.parallelism):
2863 ctx = self._blake2s(i, 0, i == (self.parallelism - 1))
2864 self._thread.append(ctx)
2866 if data:
2867 self.update(data)
2869 def _blake2s(self, ofs, depth, is_last):
2870 return blake2s(node_offset=ofs, node_depth=depth, last_node=is_last,
2871 depth=2, inner_size=32, fanout=self.parallelism)
2873 def _add_block(self, blk):
2874 self._thread[self._cur].update(blk)
2875 self._cur = (self._cur + 1) % self.parallelism
2877 def update(self, data):
2878 """Hash data.
2879 """
2880 view = memoryview(data)
2881 bs = self.block_size
2882 if self._buf:
2883 need = bs - len(self._buf)
2884 if len(view) < need:
2885 self._buf += view.tobytes()
2886 return
2887 self._add_block(self._buf + view[:need].tobytes())
2888 view = view[need:]
2889 while len(view) >= bs:
2890 self._add_block(view[:bs])
2891 view = view[bs:]
2892 self._buf = view.tobytes()
2894 def digest(self):
2895 """Return final digest value.
2896 """
2897 if self._digest is None:
2898 if self._buf:
2899 self._add_block(self._buf)
2900 self._buf = b""
2901 ctx = self._blake2s(0, 1, True)
2902 for t in self._thread:
2903 ctx.update(t.digest())
2904 self._digest = ctx.digest()
2905 return self._digest
2907 def hexdigest(self):
2908 """Hexadecimal digest."""
2909 return hexlify(self.digest()).decode("ascii")
2912class Rar3Sha1:
2913 """Emulate buggy SHA1 from RAR3.
2914 """
2915 digest_size = 20
2916 block_size = 64
2918 _BLK_BE = struct.Struct(b">16L")
2919 _BLK_LE = struct.Struct(b"<16L")
2921 __slots__ = ("_nbytes", "_md", "_rarbug")
2923 def __init__(self, data=b"", rarbug=False):
2924 self._md = sha1()
2925 self._nbytes = 0
2926 self._rarbug = rarbug
2927 self.update(data)
2929 def update(self, data):
2930 """Process more data."""
2931 self._md.update(data)
2932 bufpos = self._nbytes & 63
2933 self._nbytes += len(data)
2935 if self._rarbug and len(data) > 64:
2936 dpos = self.block_size - bufpos
2937 while dpos + self.block_size <= len(data):
2938 self._corrupt(data, dpos)
2939 dpos += self.block_size
2941 def digest(self):
2942 """Return final state."""
2943 return self._md.digest()
2945 def hexdigest(self):
2946 """Return final state as hex string."""
2947 return self._md.hexdigest()
2949 def _corrupt(self, data, dpos):
2950 """Corruption from SHA1 core."""
2951 ws = list(self._BLK_BE.unpack_from(data, dpos))
2952 for t in range(16, 80):
2953 tmp = ws[(t - 3) & 15] ^ ws[(t - 8) & 15] ^ ws[(t - 14) & 15] ^ ws[(t - 16) & 15]
2954 ws[t & 15] = ((tmp << 1) | (tmp >> (32 - 1))) & 0xFFFFFFFF
2955 self._BLK_LE.pack_into(data, dpos, *ws)
2958##
2959## Utility functions
2960##
2962S_LONG = Struct("<L")
2963S_SHORT = Struct("<H")
2964S_BYTE = Struct("<B")
2966S_BLK_HDR = Struct("<HBHH")
2967S_FILE_HDR = Struct("<LLBLLBBHL")
2968S_COMMENT_HDR = Struct("<HBBH")
2969S_OLD_SUBBLOCK_HDR = Struct("<HB")
2971def load_vint(buf, pos):
2972 """Load RAR5 variable-size int."""
2973 limit = min(pos + 11, len(buf))
2974 res = ofs = 0
2975 while pos < limit:
2976 b = buf[pos]
2977 res += ((b & 0x7F) << ofs)
2978 pos += 1
2979 ofs += 7
2980 if b < 0x80:
2981 return res, pos
2982 raise BadRarFile("cannot load vint")
2985def load_byte(buf, pos):
2986 """Load single byte"""
2987 end = pos + 1
2988 if end > len(buf):
2989 raise BadRarFile("cannot load byte")
2990 return S_BYTE.unpack_from(buf, pos)[0], end
2993def load_le32(buf, pos):
2994 """Load little-endian 32-bit integer"""
2995 end = pos + 4
2996 if end > len(buf):
2997 raise BadRarFile("cannot load le32")
2998 return S_LONG.unpack_from(buf, pos)[0], end
3001def load_bytes(buf, num, pos):
3002 """Load sequence of bytes"""
3003 end = pos + num
3004 if end > len(buf):
3005 raise BadRarFile("cannot load bytes")
3006 return buf[pos: end], end
3009def load_vstr(buf, pos):
3010 """Load bytes prefixed by vint length"""
3011 slen, pos = load_vint(buf, pos)
3012 return load_bytes(buf, slen, pos)
3015def load_dostime(buf, pos):
3016 """Load LE32 dos timestamp"""
3017 stamp, pos = load_le32(buf, pos)
3018 tup = parse_dos_time(stamp)
3019 return to_datetime(tup), pos
3022def load_unixtime(buf, pos):
3023 """Load LE32 unix timestamp"""
3024 secs, pos = load_le32(buf, pos)
3025 dt = datetime.fromtimestamp(secs, timezone.utc)
3026 return dt, pos
3029def load_windowstime(buf, pos):
3030 """Load LE64 windows timestamp"""
3031 # unix epoch (1970) in seconds from windows epoch (1601)
3032 unix_epoch = 11644473600
3033 val1, pos = load_le32(buf, pos)
3034 val2, pos = load_le32(buf, pos)
3035 secs, n1secs = divmod((val2 << 32) | val1, 10000000)
3036 dt = datetime.fromtimestamp(secs - unix_epoch, timezone.utc)
3037 dt = to_nsdatetime(dt, n1secs * 100)
3038 return dt, pos
3041#
3042# volume numbering
3043#
3045_rc_num = re.compile('^[0-9]+$')
3048def _next_newvol(volfile):
3049 """New-style next volume
3050 """
3051 name, ext = os.path.splitext(volfile)
3052 if ext.lower() in ("", ".exe", ".sfx"):
3053 volfile = name + ".rar"
3054 i = len(volfile) - 1
3055 while i >= 0:
3056 if "0" <= volfile[i] <= "9":
3057 return _inc_volname(volfile, i, False)
3058 if volfile[i] in ("/", os.sep):
3059 break
3060 i -= 1
3061 raise BadRarName("Cannot construct volume name: " + volfile)
3065def _next_oldvol(volfile):
3066 """Old-style next volume
3067 """
3068 name, ext = os.path.splitext(volfile)
3069 if ext.lower() in ("", ".exe", ".sfx"):
3070 ext = ".rar"
3071 sfx = ext[2:]
3072 if _rc_num.match(sfx):
3073 ext = _inc_volname(ext, len(ext) - 1, True)
3074 else:
3075 # .rar -> .r00
3076 ext = ext[:2] + "00"
3077 return name + ext
3080def _inc_volname(volfile, i, inc_chars):
3081 """increase digits with carry, otherwise just increment char
3082 """
3083 fn = list(volfile)
3084 while i >= 0:
3085 if fn[i] == "9":
3086 fn[i] = "0"
3087 i -= 1
3088 if i < 0:
3089 fn.insert(0, "1")
3090 elif "0" <= fn[i] < "9" or inc_chars:
3091 fn[i] = chr(ord(fn[i]) + 1)
3092 break
3093 else:
3094 fn.insert(i + 1, "1")
3095 break
3096 return "".join(fn)
3099def _parse_ext_time(h, data, pos):
3100 """Parse all RAR3 extended time fields
3101 """
3102 # flags and rest of data can be missing
3103 flags = 0
3104 if pos + 2 <= len(data):
3105 flags = S_SHORT.unpack_from(data, pos)[0]
3106 pos += 2
3108 mtime, pos = _parse_xtime(flags >> 3 * 4, data, pos, h.mtime)
3109 h.ctime, pos = _parse_xtime(flags >> 2 * 4, data, pos)
3110 h.atime, pos = _parse_xtime(flags >> 1 * 4, data, pos)
3111 h.arctime, pos = _parse_xtime(flags >> 0 * 4, data, pos)
3112 if mtime:
3113 h.mtime = mtime
3114 h.date_time = mtime.timetuple()[:6]
3115 return pos
3118def _parse_xtime(flag, data, pos, basetime=None):
3119 """Parse one RAR3 extended time field
3120 """
3121 res = None
3122 if flag & 8:
3123 if not basetime:
3124 basetime, pos = load_dostime(data, pos)
3126 # load second fractions of 100ns units
3127 rem = 0
3128 cnt = flag & 3
3129 for _ in range(cnt):
3130 b, pos = load_byte(data, pos)
3131 rem = (b << 16) | (rem >> 8)
3133 # dostime has room for 30 seconds only, correct if needed
3134 if flag & 4 and basetime.second < 59:
3135 basetime = basetime.replace(second=basetime.second + 1)
3137 res = to_nsdatetime(basetime, rem * 100)
3138 return res, pos
3141def is_filelike(obj):
3142 """Filename or file object?
3143 """
3144 if isinstance(obj, (bytes, str, Path)):
3145 return False
3146 res = True
3147 for a in ("read", "tell", "seek"):
3148 res = res and hasattr(obj, a)
3149 if not res:
3150 raise ValueError("Invalid object passed as file")
3151 return True
3154def rar5_s2k(pwd, salt, kdf_count):
3155 """String-to-key hash for RAR5.
3156 """
3157 if not isinstance(pwd, str):
3158 pwd = pwd.decode("utf8")
3159 wstr = pwd.encode("utf-16le")[:RAR_MAX_PASSWORD*2]
3160 ustr = wstr.decode("utf-16le").encode("utf8")
3161 return pbkdf2_hmac("sha256", ustr, salt, kdf_count)
3164def rar3_s2k(pwd, salt):
3165 """String-to-key hash for RAR3.
3166 """
3167 if not isinstance(pwd, str):
3168 pwd = pwd.decode("utf8")
3169 wstr = pwd.encode("utf-16le")[:RAR_MAX_PASSWORD*2]
3170 seed = bytearray(wstr + salt)
3171 h = Rar3Sha1(rarbug=True)
3172 iv = b""
3173 for i in range(16):
3174 for j in range(0x4000):
3175 cnt = S_LONG.pack(i * 0x4000 + j)
3176 h.update(seed)
3177 h.update(cnt[:3])
3178 if j == 0:
3179 iv += h.digest()[19:20]
3180 key_be = h.digest()[:16]
3181 key_le = pack("<LLLL", *unpack(">LLLL", key_be))
3182 return key_le, iv
3185def rar3_decompress(vers, meth, data, declen=0, flags=0, crc=0, pwd=None, salt=None):
3186 """Decompress blob of compressed data.
3188 Used for data with non-standard header - eg. comments.
3189 """
3190 # already uncompressed?
3191 if meth == RAR_M0 and (flags & RAR_FILE_PASSWORD) == 0:
3192 return data
3194 # take only necessary flags
3195 flags = flags & (RAR_FILE_PASSWORD | RAR_FILE_SALT | RAR_FILE_DICTMASK)
3196 flags |= RAR_LONG_BLOCK
3198 # file header
3199 fname = b"data"
3200 date = ((2010 - 1980) << 25) + (12 << 21) + (31 << 16)
3201 mode = DOS_MODE_ARCHIVE
3202 fhdr = S_FILE_HDR.pack(len(data), declen, RAR_OS_MSDOS, crc,
3203 date, vers, meth, len(fname), mode)
3204 fhdr += fname
3205 if salt:
3206 fhdr += salt
3208 # full header
3209 hlen = S_BLK_HDR.size + len(fhdr)
3210 hdr = S_BLK_HDR.pack(0, RAR_BLOCK_FILE, flags, hlen) + fhdr
3211 hcrc = crc32(hdr[2:]) & 0xFFFF
3212 hdr = S_BLK_HDR.pack(hcrc, RAR_BLOCK_FILE, flags, hlen) + fhdr
3214 # archive main header
3215 mh = S_BLK_HDR.pack(0x90CF, RAR_BLOCK_MAIN, 0, 13) + b"\0" * (2 + 4)
3217 # decompress via temp rar
3218 setup = tool_setup()
3219 tmpfd, tmpname = mkstemp(suffix=".rar", dir=HACK_TMP_DIR)
3220 tmpf = os.fdopen(tmpfd, "wb")
3221 try:
3222 tmpf.write(RAR_ID + mh + hdr + data)
3223 tmpf.close()
3225 curpwd = (flags & RAR_FILE_PASSWORD) and pwd or None
3226 cmd = setup.open_cmdline(curpwd, tmpname)
3227 p = custom_popen(cmd)
3228 return p.communicate()[0]
3229 finally:
3230 tmpf.close()
3231 os.unlink(tmpname)
3234def sanitize_filename(fname, pathsep, is_win32):
3235 """Make filename safe for write access.
3236 """
3237 if is_win32:
3238 if len(fname) > 1 and fname[1] == ":":
3239 fname = fname[2:]
3240 rc = RC_BAD_CHARS_WIN32
3241 else:
3242 rc = RC_BAD_CHARS_UNIX
3243 if rc.search(fname):
3244 fname = rc.sub("_", fname)
3246 parts = []
3247 for seg in fname.split("/"):
3248 if seg in ("", ".", ".."):
3249 continue
3250 if is_win32 and seg[-1] in (" ", "."):
3251 seg = seg[:-1] + "_"
3252 parts.append(seg)
3253 return pathsep.join(parts)
3256def empty_read(src, size, blklen):
3257 """Read and drop fixed amount of data.
3258 """
3259 while size > 0:
3260 if size > blklen:
3261 res = src.read(blklen)
3262 else:
3263 res = src.read(size)
3264 if not res:
3265 raise BadRarFile("cannot load data")
3266 size -= len(res)
3269def to_datetime(t):
3270 """Convert 6-part time tuple into datetime object.
3271 """
3272 # extract values
3273 year, mon, day, h, m, s = t
3275 # assume the values are valid
3276 try:
3277 return datetime(year, mon, day, h, m, s)
3278 except ValueError:
3279 pass
3281 # sanitize invalid values
3282 mday = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
3283 mon = max(1, min(mon, 12))
3284 day = max(1, min(day, mday[mon]))
3285 h = min(h, 23)
3286 m = min(m, 59)
3287 s = min(s, 59)
3288 return datetime(year, mon, day, h, m, s)
3291def parse_dos_time(stamp):
3292 """Parse standard 32-bit DOS timestamp.
3293 """
3294 sec, stamp = stamp & 0x1F, stamp >> 5
3295 mn, stamp = stamp & 0x3F, stamp >> 6
3296 hr, stamp = stamp & 0x1F, stamp >> 5
3297 day, stamp = stamp & 0x1F, stamp >> 5
3298 mon, stamp = stamp & 0x0F, stamp >> 4
3299 yr = (stamp & 0x7F) + 1980
3300 return (yr, mon, day, hr, mn, sec * 2)
3303# pylint: disable=arguments-differ,signature-differs
3304class nsdatetime(datetime):
3305 """Datetime that carries nanoseconds.
3307 Arithmetic operations will lose nanoseconds.
3309 .. versionadded:: 4.0
3310 """
3311 __slots__ = ("nanosecond",)
3312 nanosecond: int #: Number of nanoseconds, 0 <= nanosecond <= 999999999
3314 def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0,
3315 microsecond=0, tzinfo=None, *, fold=0, nanosecond=0):
3316 usec, mod = divmod(nanosecond, 1000) if nanosecond else (microsecond, 0)
3317 if mod == 0:
3318 return datetime(year, month, day, hour, minute, second, usec, tzinfo, fold=fold)
3319 self = super().__new__(cls, year, month, day, hour, minute, second, usec, tzinfo, fold=fold)
3320 self.nanosecond = nanosecond
3321 return self
3323 def isoformat(self, sep="T", timespec="auto"):
3324 """Formats with nanosecond precision by default.
3325 """
3326 if timespec == "auto":
3327 pre, post = super().isoformat(sep, "microseconds").split(".", 1)
3328 return f"{pre}.{self.nanosecond:09d}{post[6:]}"
3329 return super().isoformat(sep, timespec)
3331 def astimezone(self, tz=None):
3332 """Convert to new timezone.
3333 """
3334 tmp = super().astimezone(tz)
3335 return self.__class__(tmp.year, tmp.month, tmp.day, tmp.hour, tmp.minute, tmp.second,
3336 nanosecond=self.nanosecond, tzinfo=tmp.tzinfo, fold=tmp.fold)
3338 def replace(self, year=None, month=None, day=None, hour=None, minute=None, second=None,
3339 microsecond=None, tzinfo=None, *, fold=None, nanosecond=None):
3340 """Return new timestamp with specified fields replaced.
3341 """
3342 return self.__class__(
3343 self.year if year is None else year,
3344 self.month if month is None else month,
3345 self.day if day is None else day,
3346 self.hour if hour is None else hour,
3347 self.minute if minute is None else minute,
3348 self.second if second is None else second,
3349 nanosecond=((self.nanosecond if microsecond is None else microsecond * 1000)
3350 if nanosecond is None else nanosecond),
3351 tzinfo=self.tzinfo if tzinfo is None else tzinfo,
3352 fold=self.fold if fold is None else fold)
3354 def __hash__(self):
3355 return hash((super().__hash__(), self.nanosecond)) if self.nanosecond else super().__hash__()
3357 def __eq__(self, other):
3358 return super().__eq__(other) and self.nanosecond == (
3359 other.nanosecond if isinstance(other, nsdatetime) else other.microsecond * 1000)
3361 def __gt__(self, other):
3362 return super().__gt__(other) or (super().__eq__(other) and self.nanosecond > (
3363 other.nanosecond if isinstance(other, nsdatetime) else other.microsecond * 1000))
3365 def __lt__(self, other):
3366 return not (self > other or self == other)
3368 def __ge__(self, other):
3369 return not self < other
3371 def __le__(self, other):
3372 return not self > other
3374 def __ne__(self, other):
3375 return not self == other
3378def to_nsdatetime(dt, nsec):
3379 """Apply nanoseconds to datetime.
3380 """
3381 if not nsec:
3382 return dt
3383 return nsdatetime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
3384 tzinfo=dt.tzinfo, fold=dt.fold, nanosecond=nsec)
3387def to_nsecs(dt):
3388 """Convert datatime instance to nanoseconds.
3389 """
3390 secs = int(dt.timestamp())
3391 nsecs = dt.nanosecond if isinstance(dt, nsdatetime) else dt.microsecond * 1000
3392 return secs * 1000000000 + nsecs
3395def custom_popen(cmd):
3396 """Disconnect cmd from parent fds, read only from stdout.
3397 """
3398 creationflags = 0x08000000 if WIN32 else 0 # CREATE_NO_WINDOW
3399 try:
3400 p = Popen(cmd, bufsize=0, stdout=PIPE, stderr=STDOUT, stdin=DEVNULL,
3401 creationflags=creationflags)
3402 except OSError as ex:
3403 if ex.errno == errno.ENOENT:
3404 raise RarCannotExec("Unrar not installed?") from None
3405 if ex.errno == errno.EACCES or ex.errno == errno.EPERM:
3406 raise RarCannotExec("Cannot execute unrar") from None
3407 raise
3408 return p
3411def check_returncode(code, out, errmap):
3412 """Raise exception according to unrar exit code.
3413 """
3414 if code == 0:
3415 return
3417 if code > 0 and code < len(errmap):
3418 exc = errmap[code]
3419 elif code == 255:
3420 exc = RarUserBreak
3421 elif code < 0:
3422 exc = RarSignalExit
3423 else:
3424 exc = RarUnknownError
3426 # format message
3427 if out:
3428 msg = "%s [%d]: %s" % (exc.__doc__, code, out)
3429 else:
3430 msg = "%s [%d]" % (exc.__doc__, code)
3432 raise exc(msg)
3435def membuf_tempfile(memfile):
3436 """Write in-memory file object to real file.
3437 """
3438 memfile.seek(0, 0)
3440 tmpfd, tmpname = mkstemp(suffix=".rar", dir=HACK_TMP_DIR)
3441 tmpf = os.fdopen(tmpfd, "wb")
3443 try:
3444 shutil.copyfileobj(memfile, tmpf, BSIZE)
3445 tmpf.close()
3446 except BaseException:
3447 tmpf.close()
3448 os.unlink(tmpname)
3449 raise
3450 return tmpname
3453#
3454# Find working command-line tool
3455#
3457class ToolSetup:
3458 def __init__(self, setup):
3459 self.setup = setup
3461 def check(self):
3462 cmdline = self.get_cmdline("check_cmd", None)
3463 try:
3464 p = custom_popen(cmdline)
3465 out, _ = p.communicate()
3466 return p.returncode == 0
3467 except RarCannotExec:
3468 return False
3470 def open_cmdline(self, pwd, rarfn, filefn=None):
3471 cmdline = self.get_cmdline("open_cmd", pwd)
3472 cmdline.append(rarfn)
3473 if filefn:
3474 self.add_file_arg(cmdline, filefn)
3475 return cmdline
3477 def get_errmap(self):
3478 return self.setup["errmap"]
3480 def get_cmdline(self, key, pwd, nodash=False):
3481 cmdline = list(self.setup[key])
3482 cmdline[0] = globals()[cmdline[0]]
3483 if key == "check_cmd":
3484 return cmdline
3485 self.add_password_arg(cmdline, pwd)
3486 if not nodash:
3487 cmdline.append("--")
3488 return cmdline
3490 def add_file_arg(self, cmdline, filename):
3491 cmdline.append(filename)
3493 def add_password_arg(self, cmdline, pwd):
3494 """Append password switch to commandline.
3495 """
3496 if pwd is not None:
3497 if not isinstance(pwd, str):
3498 pwd = pwd.decode("utf8")
3499 args = self.setup["password"]
3500 if args is None:
3501 tool = self.setup["open_cmd"][0]
3502 raise RarCannotExec(f"{tool} does not support passwords")
3503 elif isinstance(args, str):
3504 cmdline.append(args + pwd)
3505 else:
3506 cmdline.extend(args)
3507 cmdline.append(pwd)
3508 else:
3509 cmdline.extend(self.setup["no_password"])
3512UNRAR_CONFIG = {
3513 "open_cmd": ("UNRAR_TOOL", "p", "-inul"),
3514 "check_cmd": ("UNRAR_TOOL", "-inul", "-?"),
3515 "password": "-p",
3516 "no_password": ("-p-",),
3517 # map return code to exception class, codes from rar.txt
3518 "errmap": [None,
3519 RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError, # 1..4
3520 RarWriteError, RarOpenError, RarUserError, RarMemoryError, # 5..8
3521 RarCreateError, RarNoFilesError, RarWrongPassword] # 9..11
3522}
3524# Problems with unar RAR backend:
3525# - Does not support RAR2 locked files [fails to read]
3526# - Does not support RAR5 Blake2sp hash [reading works]
3527UNAR_CONFIG = {
3528 "open_cmd": ("UNAR_TOOL", "-q", "-o", "-"),
3529 "check_cmd": ("UNAR_TOOL", "-version"),
3530 "password": ("-p",),
3531 "no_password": ("-p", ""),
3532 "errmap": [None],
3533}
3535# Problems with libarchive RAR backend:
3536# - Does not support solid archives.
3537# - Does not support password-protected archives.
3538# - Does not support RARVM-based compression filters.
3539BSDTAR_CONFIG = {
3540 "open_cmd": ("BSDTAR_TOOL", "-x", "--to-stdout", "-f"),
3541 "check_cmd": ("BSDTAR_TOOL", "--version"),
3542 "password": None,
3543 "no_password": (),
3544 "errmap": [None],
3545}
3547SEVENZIP_CONFIG = {
3548 "open_cmd": ("SEVENZIP_TOOL", "e", "-so", "-bb0"),
3549 "check_cmd": ("SEVENZIP_TOOL", "i"),
3550 "password": "-p",
3551 "no_password": ("-p",),
3552 "errmap": [None,
3553 RarWarning, RarFatalError, None, None, # 1..4
3554 None, None, RarUserError, RarMemoryError] # 5..8
3555}
3557SEVENZIP2_CONFIG = {
3558 "open_cmd": ("SEVENZIP2_TOOL", "e", "-so", "-bb0"),
3559 "check_cmd": ("SEVENZIP2_TOOL", "i"),
3560 "password": "-p",
3561 "no_password": ("-p",),
3562 "errmap": [None,
3563 RarWarning, RarFatalError, None, None, # 1..4
3564 None, None, RarUserError, RarMemoryError] # 5..8
3565}
3567CURRENT_SETUP = None
3570def tool_setup(unrar=True, unar=True, bsdtar=True, sevenzip=True, sevenzip2=True, force=False):
3571 """Pick a tool, return cached ToolSetup.
3572 """
3573 global CURRENT_SETUP
3574 if force:
3575 CURRENT_SETUP = None
3576 if CURRENT_SETUP is not None:
3577 return CURRENT_SETUP
3578 lst = []
3579 if unrar:
3580 lst.append(UNRAR_CONFIG)
3581 if unar:
3582 lst.append(UNAR_CONFIG)
3583 if sevenzip:
3584 lst.append(SEVENZIP_CONFIG)
3585 if sevenzip2:
3586 lst.append(SEVENZIP2_CONFIG)
3587 if bsdtar:
3588 lst.append(BSDTAR_CONFIG)
3590 for conf in lst:
3591 setup = ToolSetup(conf)
3592 if setup.check():
3593 CURRENT_SETUP = setup
3594 break
3595 if CURRENT_SETUP is None:
3596 raise RarCannotExec("Cannot find working tool")
3597 return CURRENT_SETUP
3600def main(args):
3601 """Minimal command-line interface for rarfile module.
3602 """
3603 import argparse
3604 p = argparse.ArgumentParser(description=main.__doc__)
3605 g = p.add_mutually_exclusive_group(required=True)
3606 g.add_argument("-l", "--list", metavar="<rarfile>",
3607 help="Show archive listing")
3608 g.add_argument("-e", "--extract", nargs=2,
3609 metavar=("<rarfile>", "<output_dir>"),
3610 help="Extract archive into target dir")
3611 g.add_argument("-t", "--test", metavar="<rarfile>",
3612 help="Test if a archive is valid")
3613 cmd = p.parse_args(args)
3615 if cmd.list:
3616 with RarFile(cmd.list) as rf:
3617 rf.printdir()
3618 elif cmd.test:
3619 with RarFile(cmd.test) as rf:
3620 rf.testrar()
3621 elif cmd.extract:
3622 with RarFile(cmd.extract[0]) as rf:
3623 rf.extractall(cmd.extract[1])
3626if __name__ == "__main__":
3627 main(sys.argv[1:])