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

2150 statements  

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. 

16 

17"""RAR archive reader. 

18 

19This is Python module for Rar archive reading. The interface 

20is made as :mod:`zipfile`-like as possible. 

21 

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. 

28 

29Example:: 

30 

31 import rarfile 

32 

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)) 

38 

39Archive files can also be accessed via file-like object returned 

40by :meth:`RarFile.open`:: 

41 

42 import rarfile 

43 

44 with rarfile.RarFile("archive.rar") as rf: 

45 with rf.open("README") as f: 

46 for ln in f: 

47 print(ln.strip()) 

48 

49For decompression to work, either ``unrar`` or ``unar`` tool must be in PATH. 

50""" 

51 

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 

67 

68AES = None 

69 

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 

83 

84 

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 

93 

94 

95__version__ = "4.4" 

96 

97# export only interesting items 

98__all__ = ["get_rar_version", "is_rarfile", "is_rarfile_sfx", "RarInfo", "RarFile", "RarExtFile"] 

99 

100## 

101## Module configuration. Can be tuned after importing. 

102## 

103 

104#: executable for unrar tool 

105UNRAR_TOOL = "unrar" 

106 

107#: executable for unar tool 

108UNAR_TOOL = "unar" 

109 

110#: executable for bsdtar tool 

111BSDTAR_TOOL = "bsdtar" 

112 

113#: executable for p7zip/7z tool 

114SEVENZIP_TOOL = "7z" 

115 

116#: executable for alternative 7z tool 

117SEVENZIP2_TOOL = "7zz" 

118 

119#: default fallback charset 

120DEFAULT_CHARSET = "windows-1252" 

121 

122#: list of encodings to try, with fallback to DEFAULT_CHARSET if none succeed 

123TRY_ENCODINGS = ("utf8", "utf-16le") 

124 

125#: whether to speed up decompression by using tmp archive 

126USE_EXTRACT_HACK = 1 

127 

128#: limit the filesize for tmp archive usage 

129HACK_SIZE_LIMIT = 20 * 1024 * 1024 

130 

131#: set specific directory for mkstemp() used by hack dir usage 

132HACK_TMP_DIR = None 

133 

134#: Separator for path name components. Always "/". 

135PATH_SEP = "/" 

136 

137## 

138## rar constants 

139## 

140 

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 # { 

152 

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 

164 

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 

186 

187# flags for RAR_BLOCK_ENDARC 

188RAR_ENDARC_NEXT_VOLUME = 0x0001 

189RAR_ENDARC_DATACRC = 0x0002 

190RAR_ENDARC_REVSPACE = 0x0004 

191RAR_ENDARC_VOLNR = 0x0008 

192 

193# flags common to all blocks 

194RAR_SKIP_IF_UNKNOWN = 0x4000 

195RAR_LONG_BLOCK = 0x8000 

196 

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 

204 

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) 

212 

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. 

220 

221RAR_MAX_PASSWORD = 127 #: Max number of utf-16 chars in passwords. 

222RAR_MAX_KDF_SHIFT = 24 #: Max power-of-2 for KDF count 

223 

224# 

225# RAR5 constants 

226# 

227 

228RAR5_BLOCK_MAIN = 1 

229RAR5_BLOCK_FILE = 2 

230RAR5_BLOCK_SERVICE = 3 

231RAR5_BLOCK_ENCRYPTION = 4 

232RAR5_BLOCK_ENDARC = 5 

233 

234RAR5_BLOCK_FLAG_EXTRA_DATA = 0x01 

235RAR5_BLOCK_FLAG_DATA_AREA = 0x02 

236RAR5_BLOCK_FLAG_SKIP_IF_UNKNOWN = 0x04 

237RAR5_BLOCK_FLAG_SPLIT_BEFORE = 0x08 

238RAR5_BLOCK_FLAG_SPLIT_AFTER = 0x10 

239RAR5_BLOCK_FLAG_DEPENDS_PREV = 0x20 

240RAR5_BLOCK_FLAG_KEEP_WITH_PARENT = 0x40 

241 

242RAR5_MAIN_FLAG_ISVOL = 0x01 

243RAR5_MAIN_FLAG_HAS_VOLNR = 0x02 

244RAR5_MAIN_FLAG_SOLID = 0x04 

245RAR5_MAIN_FLAG_RECOVERY = 0x08 

246RAR5_MAIN_FLAG_LOCKED = 0x10 

247 

248RAR5_FILE_FLAG_ISDIR = 0x01 

249RAR5_FILE_FLAG_HAS_MTIME = 0x02 

250RAR5_FILE_FLAG_HAS_CRC32 = 0x04 

251RAR5_FILE_FLAG_UNKNOWN_SIZE = 0x08 

252 

253RAR5_COMPR_SOLID = 0x40 

254 

255RAR5_ENC_FLAG_HAS_CHECKVAL = 0x01 

256 

257RAR5_ENDARC_FLAG_NEXT_VOL = 0x01 

258 

259RAR5_XFILE_ENCRYPTION = 1 

260RAR5_XFILE_HASH = 2 

261RAR5_XFILE_TIME = 3 

262RAR5_XFILE_VERSION = 4 

263RAR5_XFILE_REDIR = 5 

264RAR5_XFILE_OWNER = 6 

265RAR5_XFILE_SERVICE = 7 

266 

267RAR5_XTIME_UNIXTIME = 0x01 

268RAR5_XTIME_HAS_MTIME = 0x02 

269RAR5_XTIME_HAS_CTIME = 0x04 

270RAR5_XTIME_HAS_ATIME = 0x08 

271RAR5_XTIME_UNIXTIME_NS = 0x10 

272 

273RAR5_XENC_CIPHER_AES256 = 0 

274 

275RAR5_XENC_CHECKVAL = 0x01 

276RAR5_XENC_TWEAKED = 0x02 

277 

278RAR5_XHASH_BLAKE2SP = 0 

279 

280RAR5_XREDIR_UNIX_SYMLINK = 1 

281RAR5_XREDIR_WINDOWS_SYMLINK = 2 

282RAR5_XREDIR_WINDOWS_JUNCTION = 3 

283RAR5_XREDIR_HARD_LINK = 4 

284RAR5_XREDIR_FILE_COPY = 5 

285 

286RAR5_XREDIR_ISDIR = 0x01 

287 

288RAR5_XOWNER_UNAME = 0x01 

289RAR5_XOWNER_GNAME = 0x02 

290RAR5_XOWNER_UID = 0x04 

291RAR5_XOWNER_GID = 0x08 

292 

293RAR5_OS_WINDOWS = 0 

294RAR5_OS_UNIX = 1 

295 

296DOS_MODE_ARCHIVE = 0x20 

297DOS_MODE_DIR = 0x10 

298DOS_MODE_SYSTEM = 0x04 

299DOS_MODE_HIDDEN = 0x02 

300DOS_MODE_READONLY = 0x01 

301 

302RAR5_PW_CHECK_SIZE = 8 

303RAR5_PW_SUM_SIZE = 4 

304 

305## 

306## internal constants 

307## 

308 

309RAR_ID = b"Rar!\x1a\x07\x00" 

310RAR5_ID = b"Rar!\x1a\x07\x01\x00" 

311 

312WIN32 = sys.platform == "win32" 

313BSIZE = 512 * 1024 if WIN32 else 64 * 1024 

314 

315SFX_MAX_SIZE = 2 * 1024 * 1024 

316RAR_V3 = 3 

317RAR_V5 = 5 

318 

319_BAD_CHARS = r"""\x00-\x1F<>|"?*""" 

320RC_BAD_CHARS_UNIX = re.compile(r"[%s]" % _BAD_CHARS) 

321RC_BAD_CHARS_WIN32 = re.compile(r"[%s:^\\]" % _BAD_CHARS) 

322 

323FORCE_TOOL = False 

324 

325 

326def _find_sfx_header(xfile): 

327 sig = RAR_ID[:-1] 

328 buf = io.BytesIO() 

329 steps = (64, SFX_MAX_SIZE) 

330 

331 with XFile(xfile) as fd: 

332 for step in steps: 

333 data = fd.read(step) 

334 if not data: 

335 break 

336 buf.write(data) 

337 curdata = buf.getvalue() 

338 findpos = 0 

339 while True: 

340 pos = curdata.find(sig, findpos) 

341 if pos < 0: 

342 break 

343 if curdata[pos:pos + len(RAR_ID)] == RAR_ID: 

344 return RAR_V3, pos 

345 if curdata[pos:pos + len(RAR5_ID)] == RAR5_ID: 

346 return RAR_V5, pos 

347 findpos = pos + len(sig) 

348 fd.restore_pos() 

349 return 0, 0 

350 

351 

352## 

353## Public interface 

354## 

355 

356 

357def get_rar_version(xfile): 

358 """Check quickly whether file is rar archive. 

359 """ 

360 with XFile(xfile) as fd: 

361 buf = fd.read(len(RAR5_ID)) 

362 fd.restore_pos() 

363 if buf.startswith(RAR_ID): 

364 return RAR_V3 

365 elif buf.startswith(RAR5_ID): 

366 return RAR_V5 

367 return 0 

368 

369 

370def is_rarfile(xfile): 

371 """Check quickly whether file is rar archive. 

372 """ 

373 try: 

374 return get_rar_version(xfile) > 0 

375 except OSError: 

376 # File not found or not accessible, ignore 

377 return False 

378 

379 

380def is_rarfile_sfx(xfile): 

381 """Check whether file is rar archive with support for SFX. 

382 

383 It will read 2M from file. 

384 """ 

385 return _find_sfx_header(xfile)[0] > 0 

386 

387 

388class Error(Exception): 

389 """Base class for rarfile errors.""" 

390 

391 

392class BadRarFile(Error): 

393 """Incorrect data in archive.""" 

394 

395 

396class NotRarFile(Error): 

397 """The file is not RAR archive.""" 

398 

399 

400class BadRarName(Error): 

401 """Cannot guess multipart name components.""" 

402 

403 

404class NoRarEntry(Error): 

405 """File not found in RAR""" 

406 

407 

408class PasswordRequired(Error): 

409 """File requires password""" 

410 

411 

412class BadSymLinkError(Error): 

413 """Invalid symbolic link""" 

414 

415 

416class NeedFirstVolume(Error): 

417 """Need to start from first volume. 

418 

419 Attributes: 

420 

421 current_volume 

422 Volume number of current file or None if not known 

423 """ 

424 def __init__(self, msg, volume): 

425 super().__init__(msg) 

426 self.current_volume = volume 

427 

428 

429class NoCrypto(Error): 

430 """Cannot parse encrypted headers - no crypto available.""" 

431 

432 

433class RarExecError(Error): 

434 """Problem reported by unrar/rar.""" 

435 

436 

437class RarWarning(RarExecError): 

438 """Non-fatal error""" 

439 

440 

441class RarFatalError(RarExecError): 

442 """Fatal error""" 

443 

444 

445class RarCRCError(RarExecError): 

446 """CRC error during unpacking""" 

447 

448 

449class RarLockedArchiveError(RarExecError): 

450 """Must not modify locked archive""" 

451 

452 

453class RarWriteError(RarExecError): 

454 """Write error""" 

455 

456 

457class RarOpenError(RarExecError): 

458 """Open error""" 

459 

460 

461class RarUserError(RarExecError): 

462 """User error""" 

463 

464 

465class RarMemoryError(RarExecError): 

466 """Memory error""" 

467 

468 

469class RarCreateError(RarExecError): 

470 """Create error""" 

471 

472 

473class RarNoFilesError(RarExecError): 

474 """No files that match pattern were found""" 

475 

476 

477class RarUserBreak(RarExecError): 

478 """User stop""" 

479 

480 

481class RarWrongPassword(RarExecError): 

482 """Incorrect password""" 

483 

484 

485class RarUnknownError(RarExecError): 

486 """Unknown exit code""" 

487 

488 

489class RarSignalExit(RarExecError): 

490 """Unrar exited with signal""" 

491 

492 

493class RarCannotExec(RarExecError): 

494 """Executable not found.""" 

495 

496 

497class UnsupportedWarning(UserWarning): 

498 """Archive uses feature that are unsupported by rarfile. 

499 

500 .. versionadded:: 4.0 

501 """ 

502 

503 

504class RarInfo: 

505 r"""An entry in rar archive. 

506 

507 Timestamps as :class:`~datetime.datetime` are without timezone in RAR3, 

508 with UTC timezone in RAR5 archives. 

509 

510 Attributes: 

511 

512 filename 

513 File name with relative path. 

514 Path separator is "/". Always unicode string. 

515 

516 date_time 

517 File modification timestamp. As tuple of (year, month, day, hour, minute, second). 

518 RAR5 allows archives where it is missing, it's None then. 

519 

520 comment 

521 Optional file comment field. Unicode string. (RAR3-only) 

522 

523 file_size 

524 Uncompressed size. 

525 

526 compress_size 

527 Compressed size. 

528 

529 compress_type 

530 Compression method: one of :data:`RAR_M0` .. :data:`RAR_M5` constants. 

531 

532 extract_version 

533 Minimal Rar version needed for decompressing. As (major*10 + minor), 

534 so 2.9 is 29. 

535 

536 RAR3: 10, 20, 29 

537 

538 RAR5 does not have such field in archive, it's simply set to 50. 

539 

540 host_os 

541 Host OS type, one of RAR_OS_* constants. 

542 

543 RAR3: :data:`RAR_OS_WIN32`, :data:`RAR_OS_UNIX`, :data:`RAR_OS_MSDOS`, 

544 :data:`RAR_OS_OS2`, :data:`RAR_OS_BEOS`. 

545 

546 RAR5: :data:`RAR_OS_WIN32`, :data:`RAR_OS_UNIX`. 

547 

548 mode 

549 File attributes. May be either dos-style or unix-style, depending on host_os. 

550 

551 mtime 

552 File modification time. Same value as :attr:`date_time` 

553 but as :class:`~datetime.datetime` object with extended precision. 

554 

555 ctime 

556 Optional time field: creation time. As :class:`~datetime.datetime` object. 

557 

558 atime 

559 Optional time field: last access time. As :class:`~datetime.datetime` object. 

560 

561 arctime 

562 Optional time field: archival time. As :class:`~datetime.datetime` object. 

563 (RAR3-only) 

564 

565 CRC 

566 CRC-32 of uncompressed file, unsigned int. 

567 

568 RAR5: may be None. 

569 

570 blake2sp_hash 

571 Blake2SP hash over decompressed data. (RAR5-only) 

572 

573 volume 

574 Volume nr, starting from 0. 

575 

576 volume_file 

577 Volume file name, where file starts. 

578 

579 file_redir 

580 If not None, file is link of some sort. Contains tuple of (type, flags, target). 

581 (RAR5-only) 

582 

583 Type is one of constants: 

584 

585 :data:`RAR5_XREDIR_UNIX_SYMLINK` 

586 Unix symlink. 

587 :data:`RAR5_XREDIR_WINDOWS_SYMLINK` 

588 Windows symlink. 

589 :data:`RAR5_XREDIR_WINDOWS_JUNCTION` 

590 Windows junction. 

591 :data:`RAR5_XREDIR_HARD_LINK` 

592 Hard link to target. 

593 :data:`RAR5_XREDIR_FILE_COPY` 

594 Current file is copy of another archive entry. 

595 

596 Flags may contain bits: 

597 

598 :data:`RAR5_XREDIR_ISDIR` 

599 Symlink points to directory. 

600 """ 

601 

602 # zipfile-compatible fields 

603 filename = None 

604 file_size = None 

605 compress_size = None 

606 date_time = None 

607 CRC = None 

608 volume = None 

609 orig_filename = None 

610 

611 # optional extended time fields, datetime() objects. 

612 mtime = None 

613 ctime = None 

614 atime = None 

615 

616 extract_version = None 

617 mode = None 

618 host_os = None 

619 compress_type = None 

620 

621 # rar3-only fields 

622 comment = None 

623 arctime = None 

624 

625 # rar5-only fields 

626 blake2sp_hash = None 

627 file_redir = None 

628 

629 # internal fields 

630 flags = 0 

631 type = None 

632 

633 # zipfile compat 

634 def is_dir(self): 

635 """Returns True if entry is a directory. 

636 

637 .. versionadded:: 4.0 

638 """ 

639 return False 

640 

641 def is_symlink(self): 

642 """Returns True if entry is a symlink. 

643 

644 .. versionadded:: 4.0 

645 """ 

646 return False 

647 

648 def is_file(self): 

649 """Returns True if entry is a normal file. 

650 

651 .. versionadded:: 4.0 

652 """ 

653 return False 

654 

655 def needs_password(self): 

656 """Returns True if data is stored password-protected. 

657 """ 

658 if self.type == RAR_BLOCK_FILE: 

659 return (self.flags & RAR_FILE_PASSWORD) > 0 

660 return False 

661 

662 def isdir(self): 

663 """Returns True if entry is a directory. 

664 

665 .. deprecated:: 4.0 

666 """ 

667 return self.is_dir() 

668 

669 

670class RarFile: 

671 """Parse RAR structure, provide access to files in archive. 

672 

673 Parameters: 

674 

675 file 

676 archive file name or file-like object. 

677 mode 

678 only "r" is supported. 

679 charset 

680 fallback charset to use, if filenames are not already Unicode-enabled. 

681 info_callback 

682 debug callback, gets to see all archive entries. 

683 crc_check 

684 set to False to disable CRC checks 

685 errors 

686 Either "stop" to quietly stop parsing on errors, 

687 or "strict" to raise errors. Default is "stop". 

688 part_only 

689 If True, read only single file and allow it to be middle-part 

690 of multi-volume archive. 

691 

692 .. versionadded:: 4.0 

693 """ 

694 

695 #: File name, if available. Unicode string or None. 

696 filename = None 

697 

698 #: Archive comment. Unicode string or None. 

699 comment = None 

700 

701 def __init__(self, file, mode="r", charset=None, info_callback=None, 

702 crc_check=True, errors="stop", part_only=False): 

703 if is_filelike(file): 

704 self.filename = getattr(file, "name", None) 

705 else: 

706 if isinstance(file, Path): 

707 file = str(file) 

708 self.filename = file 

709 self._rarfile = file 

710 

711 self._charset = charset or DEFAULT_CHARSET 

712 self._info_callback = info_callback 

713 self._crc_check = crc_check 

714 self._part_only = part_only 

715 self._password = None 

716 self._file_parser = None 

717 

718 if errors == "stop": 

719 self._strict = False 

720 elif errors == "strict": 

721 self._strict = True 

722 else: 

723 raise ValueError("Invalid value for errors= parameter.") 

724 

725 if mode != "r": 

726 raise NotImplementedError("RarFile supports only mode=r") 

727 

728 self._parse() 

729 

730 def __enter__(self): 

731 """Open context.""" 

732 return self 

733 

734 def __exit__(self, typ, value, traceback): 

735 """Exit context.""" 

736 self.close() 

737 

738 def __iter__(self): 

739 """Iterate over members.""" 

740 return iter(self.infolist()) 

741 

742 def setpassword(self, pwd): 

743 """Sets the password to use when extracting. 

744 """ 

745 self._password = pwd 

746 if self._file_parser: 

747 if self._file_parser.has_header_encryption(): 

748 self._file_parser = None 

749 if not self._file_parser: 

750 self._parse() 

751 else: 

752 self._file_parser.setpassword(self._password) 

753 

754 def needs_password(self): 

755 """Returns True if any archive entries require password for extraction. 

756 """ 

757 return self._file_parser.needs_password() 

758 

759 def is_solid(self): 

760 """Returns True if archive uses solid compression. 

761 

762 .. versionadded:: 4.2 

763 """ 

764 return self._file_parser.is_solid() 

765 

766 def namelist(self): 

767 """Return list of filenames in archive. 

768 """ 

769 return [f.filename for f in self.infolist()] 

770 

771 def infolist(self): 

772 """Return RarInfo objects for all files/directories in archive. 

773 """ 

774 return self._file_parser.infolist() 

775 

776 def volumelist(self): 

777 """Returns filenames of archive volumes. 

778 

779 In case of single-volume archive, the list contains 

780 just the name of main archive file. 

781 """ 

782 return self._file_parser.volumelist() 

783 

784 def getinfo(self, name): 

785 """Return RarInfo for file. 

786 """ 

787 return self._file_parser.getinfo(name) 

788 

789 def getinfo_orig(self, name): 

790 """Return RarInfo for file source. 

791 

792 RAR5: if name is hard-linked or copied file, 

793 returns original entry with original filename. 

794 

795 .. versionadded:: 4.1 

796 """ 

797 return self._file_parser.getinfo_orig(name) 

798 

799 def open(self, name, mode="r", pwd=None): 

800 """Returns file-like object (:class:`RarExtFile`) from where the data can be read. 

801 

802 The object implements :class:`io.RawIOBase` interface, so it can 

803 be further wrapped with :class:`io.BufferedReader` 

804 and :class:`io.TextIOWrapper`. 

805 

806 On older Python where io module is not available, it implements 

807 only .read(), .seek(), .tell() and .close() methods. 

808 

809 The object is seekable, although the seeking is fast only on 

810 uncompressed files, on compressed files the seeking is implemented 

811 by reading ahead and/or restarting the decompression. 

812 

813 Parameters: 

814 

815 name 

816 file name or RarInfo instance. 

817 mode 

818 must be "r" 

819 pwd 

820 password to use for extracting. 

821 """ 

822 

823 if mode != "r": 

824 raise NotImplementedError("RarFile.open() supports only mode=r") 

825 

826 # entry lookup 

827 inf = self.getinfo(name) 

828 if inf.is_dir(): 

829 raise io.UnsupportedOperation("Directory does not have any data: " + inf.filename) 

830 

831 # check password 

832 if inf.needs_password(): 

833 pwd = pwd or self._password 

834 if pwd is None: 

835 raise PasswordRequired("File %s requires password" % inf.filename) 

836 else: 

837 pwd = None 

838 

839 return self._file_parser.open(inf, pwd) 

840 

841 def read(self, name, pwd=None): 

842 """Return uncompressed data for archive entry. 

843 

844 For longer files using :meth:`~RarFile.open` may be better idea. 

845 

846 Parameters: 

847 

848 name 

849 filename or RarInfo instance 

850 pwd 

851 password to use for extracting. 

852 """ 

853 

854 with self.open(name, "r", pwd) as f: 

855 return f.read() 

856 

857 def close(self): 

858 """Release open resources.""" 

859 pass 

860 

861 def printdir(self, file=None): 

862 """Print archive file list to stdout or given file. 

863 """ 

864 if file is None: 

865 file = sys.stdout 

866 for f in self.infolist(): 

867 print(f.filename, file=file) 

868 

869 def extract(self, member, path=None, pwd=None): 

870 """Extract single file into current directory. 

871 

872 Parameters: 

873 

874 member 

875 filename or :class:`RarInfo` instance 

876 path 

877 optional destination path 

878 pwd 

879 optional password to use 

880 """ 

881 inf = self.getinfo(member) 

882 return self._extract_one(inf, path, pwd, True) 

883 

884 def extractall(self, path=None, members=None, pwd=None): 

885 """Extract all files into current directory. 

886 

887 Parameters: 

888 

889 path 

890 optional destination path 

891 members 

892 optional filename or :class:`RarInfo` instance list to extract 

893 pwd 

894 optional password to use 

895 """ 

896 if members is None: 

897 members = self.namelist() 

898 

899 done = set() 

900 dirs = [] 

901 for m in members: 

902 inf = self.getinfo(m) 

903 dst = self._extract_one(inf, path, pwd, not inf.is_dir()) 

904 if inf.is_dir(): 

905 if dst not in done: 

906 dirs.append((dst, inf)) 

907 done.add(dst) 

908 if dirs: 

909 dirs.sort(reverse=True) 

910 for dst, inf in dirs: 

911 self._set_attrs(inf, dst) 

912 

913 def testrar(self, pwd=None): 

914 """Read all files and test CRC. 

915 """ 

916 for member in self.infolist(): 

917 if member.is_file(): 

918 with self.open(member, 'r', pwd) as f: 

919 empty_read(f, member.file_size, BSIZE) 

920 

921 def strerror(self): 

922 """Return error string if parsing failed or None if no problems. 

923 """ 

924 if not self._file_parser: 

925 return "Not a RAR file" 

926 return self._file_parser.strerror() 

927 

928 ## 

929 ## private methods 

930 ## 

931 

932 def _parse(self): 

933 """Run parser for file type 

934 """ 

935 ver, sfx_ofs = _find_sfx_header(self._rarfile) 

936 if ver == RAR_V3: 

937 p3 = RAR3Parser(self._rarfile, self._password, self._crc_check, 

938 self._charset, self._strict, self._info_callback, 

939 sfx_ofs, self._part_only) 

940 self._file_parser = p3 # noqa 

941 elif ver == RAR_V5: 

942 p5 = RAR5Parser(self._rarfile, self._password, self._crc_check, 

943 self._charset, self._strict, self._info_callback, 

944 sfx_ofs, self._part_only) 

945 self._file_parser = p5 # noqa 

946 else: 

947 raise NotRarFile("Not a RAR file") 

948 

949 self._file_parser.parse() 

950 self.comment = self._file_parser.comment 

951 

952 def _extract_one(self, info, path, pwd, set_attrs): 

953 fname = sanitize_filename( 

954 info.filename, os.path.sep, WIN32 

955 ) 

956 

957 if path is None: 

958 path = os.getcwd() 

959 else: 

960 path = os.fspath(path) 

961 dstfn = os.path.join(path, fname) 

962 

963 # Reject members whose destination escapes `path` once symlinks 

964 # already created on disk are resolved. Without this, a symlink 

965 # member can point outside `path` and a later file/dir member 

966 # named through it will be written outside the extraction root. 

967 real_path = os.path.realpath(path) 

968 real_dst = os.path.realpath(dstfn) 

969 if real_dst != real_path and not real_dst.startswith(real_path + os.sep): 

970 raise BadRarFile( 

971 "Refusing to extract entry that escapes destination: %r" % info.filename 

972 ) 

973 

974 dirname = os.path.dirname(dstfn) 

975 if dirname and dirname != ".": 

976 os.makedirs(dirname, exist_ok=True) 

977 

978 if info.is_file(): 

979 return self._make_file(info, dstfn, pwd, set_attrs) 

980 if info.is_dir(): 

981 return self._make_dir(info, dstfn, pwd, set_attrs) 

982 if info.is_symlink(): 

983 return self._make_symlink(info, dstfn, pwd, set_attrs, path) 

984 return None 

985 

986 def _create_helper(self, name, flags, info): 

987 return os.open(name, flags) 

988 

989 def _make_file(self, info, dstfn, pwd, set_attrs): 

990 def helper(name, flags): 

991 return self._create_helper(name, flags, info) 

992 with self.open(info, "r", pwd) as src: 

993 with open(dstfn, "wb", opener=helper) as dst: 

994 shutil.copyfileobj(src, dst) 

995 if set_attrs: 

996 self._set_attrs(info, dstfn) 

997 return dstfn 

998 

999 def _make_dir(self, info, dstfn, pwd, set_attrs): 

1000 os.makedirs(dstfn, exist_ok=True) 

1001 if set_attrs: 

1002 self._set_attrs(info, dstfn) 

1003 return dstfn 

1004 

1005 def _make_symlink(self, info, dstfn, pwd, set_attrs, top): 

1006 target_is_directory = False 

1007 if info.host_os == RAR_OS_UNIX: 

1008 link_name = self.read(info, pwd).decode("utf8", "replace") 

1009 target_is_directory = (info.flags & RAR_FILE_DIRECTORY) == RAR_FILE_DIRECTORY 

1010 elif info.file_redir: 

1011 redir_type, redir_flags, link_name = info.file_redir 

1012 if redir_type == RAR5_XREDIR_WINDOWS_JUNCTION: 

1013 warnings.warn(f"Windows junction not supported - {info.filename}", UnsupportedWarning) 

1014 return None 

1015 target_is_directory = (redir_type & RAR5_XREDIR_ISDIR) > 0 

1016 else: 

1017 warnings.warn(f"Unsupported link type - {info.filename}", UnsupportedWarning) 

1018 return None 

1019 

1020 # disallow abs paths 

1021 target = os.path.normpath(link_name) 

1022 if os.path.isabs(target) or os.path.splitdrive(target)[0]: 

1023 raise BadSymLinkError('Absolute links not allowed') 

1024 

1025 # disallow ../ traversal 

1026 dest_abs = os.path.realpath(top) 

1027 target_base = os.path.dirname(dstfn) 

1028 target_abs = os.path.realpath(os.path.join(target_base, target)) 

1029 if os.path.commonpath([target_abs, dest_abs]) != dest_abs: 

1030 raise BadSymLinkError('Link to outside not allowed') 

1031 

1032 os.symlink(link_name, dstfn, target_is_directory=target_is_directory) 

1033 return dstfn 

1034 

1035 def _set_attrs(self, info, dstfn): 

1036 if info.host_os == RAR_OS_UNIX: 

1037 os.chmod(dstfn, info.mode & 0o777) 

1038 elif info.host_os in (RAR_OS_WIN32, RAR_OS_MSDOS): 

1039 # only keep R/O attr, except for dirs on win32 

1040 if info.mode & DOS_MODE_READONLY and (info.is_file() or not WIN32): 

1041 st = os.stat(dstfn) 

1042 new_mode = st.st_mode & ~0o222 

1043 os.chmod(dstfn, new_mode) 

1044 

1045 if info.mtime: 

1046 mtime_ns = to_nsecs(info.mtime) 

1047 atime_ns = to_nsecs(info.atime) if info.atime else mtime_ns 

1048 os.utime(dstfn, ns=(atime_ns, mtime_ns)) 

1049 

1050 

1051# 

1052# File format parsing 

1053# 

1054 

1055class CommonParser: 

1056 """Shared parser parts.""" 

1057 _main = None 

1058 _hdrenc_main = None 

1059 _needs_password = False 

1060 _fd = None 

1061 _expect_sig = None 

1062 _parse_error = None 

1063 _password = None 

1064 comment = None 

1065 

1066 def __init__(self, rarfile, password, crc_check, charset, strict, 

1067 info_cb, sfx_offset, part_only): 

1068 self._rarfile = rarfile 

1069 self._password = password 

1070 self._crc_check = crc_check 

1071 self._charset = charset 

1072 self._strict = strict 

1073 self._info_callback = info_cb 

1074 self._info_list = [] 

1075 self._info_map = {} 

1076 self._vol_list = [] 

1077 self._sfx_offset = sfx_offset 

1078 self._part_only = part_only 

1079 

1080 def is_solid(self): 

1081 """Returns True if archive uses solid compression. 

1082 """ 

1083 if self._main: 

1084 if self._main.flags & RAR_MAIN_SOLID: 

1085 return True 

1086 return False 

1087 

1088 def has_header_encryption(self): 

1089 """Returns True if headers are encrypted 

1090 """ 

1091 if self._hdrenc_main: 

1092 return True 

1093 if self._main: 

1094 if self._main.flags & RAR_MAIN_PASSWORD: 

1095 return True 

1096 return False 

1097 

1098 def setpassword(self, pwd): 

1099 """Set cached password.""" 

1100 self._password = pwd 

1101 

1102 def volumelist(self): 

1103 """Volume files""" 

1104 return self._vol_list 

1105 

1106 def needs_password(self): 

1107 """Is password required""" 

1108 return self._needs_password 

1109 

1110 def strerror(self): 

1111 """Last error""" 

1112 return self._parse_error 

1113 

1114 def infolist(self): 

1115 """List of RarInfo records. 

1116 """ 

1117 return self._info_list 

1118 

1119 def getinfo(self, member): 

1120 """Return RarInfo for filename 

1121 """ 

1122 if isinstance(member, RarInfo): 

1123 fname = member.filename 

1124 elif isinstance(member, Path): 

1125 fname = str(member) 

1126 else: 

1127 fname = member 

1128 

1129 if fname.endswith("/"): 

1130 fname = fname.rstrip("/") 

1131 

1132 try: 

1133 return self._info_map[fname] 

1134 except KeyError: 

1135 raise NoRarEntry("No such file: %s" % fname) from None 

1136 

1137 def getinfo_orig(self, member): 

1138 inf = self.getinfo(member) 

1139 if inf.file_redir: 

1140 redir_type, redir_flags, redir_name = inf.file_redir 

1141 # cannot leave to unrar as it expects copied file to exist 

1142 if redir_type in (RAR5_XREDIR_FILE_COPY, RAR5_XREDIR_HARD_LINK): 

1143 inf = self.getinfo(redir_name) 

1144 return inf 

1145 

1146 def parse(self): 

1147 """Process file.""" 

1148 self._fd = None 

1149 try: 

1150 self._parse_real() 

1151 finally: 

1152 if self._fd: 

1153 self._fd.close() 

1154 self._fd = None 

1155 

1156 def _parse_real(self): 

1157 """Actually read file. 

1158 """ 

1159 fd = XFile(self._rarfile) 

1160 self._fd = fd 

1161 fd.seek(self._sfx_offset, 0) 

1162 sig = fd.read(len(self._expect_sig)) 

1163 if sig != self._expect_sig: 

1164 raise NotRarFile("Not a Rar archive") 

1165 

1166 volume = 0 # first vol (.rar) is 0 

1167 more_vols = False 

1168 endarc = False 

1169 volfile = self._rarfile 

1170 self._vol_list = [self._rarfile] 

1171 raise_need_first_vol = False 

1172 while True: 

1173 if endarc: 

1174 h = None # don"t read past ENDARC 

1175 else: 

1176 h = self._parse_header(fd) 

1177 if not h: 

1178 if raise_need_first_vol: 

1179 # did not find ENDARC with VOLNR 

1180 raise NeedFirstVolume("Need to start from first volume", None) 

1181 if more_vols and not self._part_only: 

1182 volume += 1 

1183 fd.close() 

1184 try: 

1185 volfile = self._next_volname(volfile) 

1186 fd = XFile(volfile) 

1187 except IOError: 

1188 self._set_error("Cannot open next volume: %s", volfile) 

1189 break 

1190 self._fd = fd 

1191 sig = fd.read(len(self._expect_sig)) 

1192 if sig != self._expect_sig: 

1193 self._set_error("Invalid volume sig: %s", volfile) 

1194 break 

1195 more_vols = False 

1196 endarc = False 

1197 self._vol_list.append(volfile) 

1198 self._main = None 

1199 self._hdrenc_main = None 

1200 continue 

1201 break 

1202 h.volume = volume 

1203 h.volume_file = volfile 

1204 

1205 if h.type == RAR_BLOCK_MAIN and not self._main: 

1206 self._main = h 

1207 if volume == 0 and (h.flags & RAR_MAIN_NEWNUMBERING) and not self._part_only: 

1208 # RAR 2.x does not set FIRSTVOLUME, 

1209 # so check it only if NEWNUMBERING is used 

1210 if (h.flags & RAR_MAIN_FIRSTVOLUME) == 0: 

1211 if getattr(h, "main_volume_number", None) is not None: 

1212 # rar5 may have more info 

1213 raise NeedFirstVolume( 

1214 "Need to start from first volume (current: %r)" 

1215 % (h.main_volume_number,), 

1216 h.main_volume_number 

1217 ) 

1218 # delay raise until we have volnr from ENDARC 

1219 raise_need_first_vol = True 

1220 if h.flags & RAR_MAIN_PASSWORD: 

1221 self._needs_password = True 

1222 if not self._password: 

1223 break 

1224 elif h.type == RAR_BLOCK_ENDARC: 

1225 # use flag, but also allow RAR 2.x logic below to trigger 

1226 if h.flags & RAR_ENDARC_NEXT_VOLUME: 

1227 more_vols = True 

1228 endarc = True 

1229 if raise_need_first_vol and (h.flags & RAR_ENDARC_VOLNR) > 0: 

1230 raise NeedFirstVolume( 

1231 "Need to start from first volume (current: %r)" 

1232 % (h.endarc_volnr,), 

1233 h.endarc_volnr 

1234 ) 

1235 elif h.type == RAR_BLOCK_FILE: 

1236 # RAR 2.x does not write RAR_BLOCK_ENDARC 

1237 if h.flags & RAR_FILE_SPLIT_AFTER: 

1238 more_vols = True 

1239 # RAR 2.x does not set RAR_MAIN_FIRSTVOLUME 

1240 if volume == 0 and h.flags & RAR_FILE_SPLIT_BEFORE: 

1241 if not self._part_only: 

1242 raise_need_first_vol = True 

1243 

1244 if h.needs_password(): 

1245 self._needs_password = True 

1246 

1247 # store it 

1248 self.process_entry(fd, h) 

1249 

1250 if self._info_callback: 

1251 self._info_callback(h) 

1252 

1253 # go to next header 

1254 if h.add_size > 0: 

1255 fd.seek(h.data_offset + h.add_size, 0) 

1256 

1257 def process_entry(self, fd, item): 

1258 """Examine item, add into lookup cache.""" 

1259 raise NotImplementedError() 

1260 

1261 def _decrypt_header(self, fd): 

1262 raise NotImplementedError("_decrypt_header") 

1263 

1264 def _parse_block_header(self, fd): 

1265 raise NotImplementedError("_parse_block_header") 

1266 

1267 def _open_hack(self, inf, pwd): 

1268 raise NotImplementedError("_open_hack") 

1269 

1270 def _parse_header(self, fd): 

1271 """Read single header 

1272 """ 

1273 try: 

1274 # handle encrypted headers 

1275 if (self._main and self._main.flags & RAR_MAIN_PASSWORD) or self._hdrenc_main: 

1276 if not self._password: 

1277 return None 

1278 fd = self._decrypt_header(fd) 

1279 

1280 # now read actual header 

1281 return self._parse_block_header(fd) 

1282 except struct.error: 

1283 self._set_error("Broken header in RAR file") 

1284 return None 

1285 

1286 def _next_volname(self, volfile): 

1287 """Given current vol name, construct next one 

1288 """ 

1289 if is_filelike(volfile): 

1290 raise IOError("Working on single FD") 

1291 if self._main.flags & RAR_MAIN_NEWNUMBERING: 

1292 return _next_newvol(volfile) 

1293 return _next_oldvol(volfile) 

1294 

1295 def _set_error(self, msg, *args): 

1296 if args: 

1297 msg = msg % args 

1298 self._parse_error = msg 

1299 if self._strict: 

1300 raise BadRarFile(msg) 

1301 

1302 def open(self, inf, pwd): 

1303 """Return stream object for file data.""" 

1304 

1305 if inf.file_redir: 

1306 redir_type, redir_flags, redir_name = inf.file_redir 

1307 # cannot leave to unrar as it expects copied file to exist 

1308 if redir_type in (RAR5_XREDIR_FILE_COPY, RAR5_XREDIR_HARD_LINK): 

1309 inf = self.getinfo(redir_name) 

1310 if not inf: 

1311 raise BadRarFile("cannot find copied file") 

1312 elif redir_type in ( 

1313 RAR5_XREDIR_UNIX_SYMLINK, RAR5_XREDIR_WINDOWS_SYMLINK, 

1314 RAR5_XREDIR_WINDOWS_JUNCTION, 

1315 ): 

1316 return io.BytesIO(redir_name.encode("utf8")) 

1317 if inf.flags & RAR_FILE_SPLIT_BEFORE: 

1318 raise NeedFirstVolume("Partial file, please start from first volume: " + inf.filename, None) 

1319 

1320 # is temp write usable? 

1321 use_hack = 1 

1322 if not self._main: 

1323 use_hack = 0 

1324 elif self._main._must_disable_hack(): 

1325 use_hack = 0 

1326 elif inf._must_disable_hack(): 

1327 use_hack = 0 

1328 elif is_filelike(self._rarfile): 

1329 pass 

1330 elif inf.file_size > HACK_SIZE_LIMIT: 

1331 use_hack = 0 

1332 elif not USE_EXTRACT_HACK: 

1333 use_hack = 0 

1334 

1335 # now extract 

1336 if inf.compress_type == RAR_M0 and (inf.flags & RAR_FILE_PASSWORD) == 0 and inf.file_redir is None: 

1337 return self._open_clear(inf) 

1338 elif use_hack: 

1339 return self._open_hack(inf, pwd) 

1340 elif is_filelike(self._rarfile): 

1341 return self._open_unrar_membuf(self._rarfile, inf, pwd) 

1342 else: 

1343 return self._open_unrar(self._rarfile, inf, pwd) 

1344 

1345 def _open_clear(self, inf): 

1346 if FORCE_TOOL: 

1347 return self._open_unrar(self._rarfile, inf) 

1348 return DirectReader(self, inf) 

1349 

1350 def _open_hack_core(self, inf, pwd, prefix, suffix): 

1351 

1352 size = inf.compress_size + inf.header_size 

1353 rf = XFile(inf.volume_file, 0) 

1354 rf.seek(inf.header_offset) 

1355 

1356 tmpfd, tmpname = mkstemp(suffix=".rar", dir=HACK_TMP_DIR) 

1357 tmpf = os.fdopen(tmpfd, "wb") 

1358 

1359 try: 

1360 tmpf.write(prefix) 

1361 while size > 0: 

1362 if size > BSIZE: 

1363 buf = rf.read(BSIZE) 

1364 else: 

1365 buf = rf.read(size) 

1366 if not buf: 

1367 raise BadRarFile("read failed: " + inf.filename) 

1368 tmpf.write(buf) 

1369 size -= len(buf) 

1370 tmpf.write(suffix) 

1371 tmpf.close() 

1372 rf.close() 

1373 except BaseException: 

1374 rf.close() 

1375 tmpf.close() 

1376 os.unlink(tmpname) 

1377 raise 

1378 

1379 return self._open_unrar(tmpname, inf, pwd, tmpname) 

1380 

1381 def _open_unrar_membuf(self, memfile, inf, pwd): 

1382 """Write in-memory archive to temp file, needed for solid archives. 

1383 """ 

1384 tmpname = membuf_tempfile(memfile) 

1385 return self._open_unrar(tmpname, inf, pwd, tmpname, force_file=True) 

1386 

1387 def _open_unrar(self, rarfile, inf, pwd=None, tmpfile=None, force_file=False): 

1388 """Extract using unrar 

1389 """ 

1390 setup = tool_setup() 

1391 

1392 # not giving filename avoids encoding related problems 

1393 fn = None 

1394 if not tmpfile or force_file: 

1395 fn = inf.filename.replace("/", os.path.sep) 

1396 

1397 # read from unrar pipe 

1398 cmd = setup.open_cmdline(pwd, rarfile, fn) 

1399 return PipeReader(self, inf, cmd, tmpfile) 

1400 

1401 

1402# 

1403# RAR3 format 

1404# 

1405 

1406class Rar3Info(RarInfo): 

1407 """RAR3 specific fields.""" 

1408 extract_version = 15 

1409 salt = None 

1410 add_size = 0 

1411 header_crc = None 

1412 header_size = None 

1413 header_offset = None 

1414 data_offset = None 

1415 _md_class = None 

1416 _md_expect = None 

1417 _name_size = None 

1418 

1419 # make sure some rar5 fields are always present 

1420 file_redir = None 

1421 blake2sp_hash = None 

1422 

1423 endarc_datacrc = None 

1424 endarc_volnr = None 

1425 

1426 old_sub_type = None 

1427 

1428 def _must_disable_hack(self): 

1429 if self.type == RAR_BLOCK_FILE: 

1430 if self.flags & RAR_FILE_PASSWORD: 

1431 return True 

1432 elif self.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER): 

1433 return True 

1434 elif self.type == RAR_BLOCK_MAIN: 

1435 if self.flags & (RAR_MAIN_SOLID | RAR_MAIN_PASSWORD): 

1436 return True 

1437 return False 

1438 

1439 def is_dir(self): 

1440 """Returns True if entry is a directory.""" 

1441 if self.type == RAR_BLOCK_FILE and not self.is_symlink(): 

1442 return (self.flags & RAR_FILE_DIRECTORY) == RAR_FILE_DIRECTORY 

1443 return False 

1444 

1445 def is_symlink(self): 

1446 """Returns True if entry is a symlink.""" 

1447 return ( 

1448 self.type == RAR_BLOCK_FILE and 

1449 self.host_os == RAR_OS_UNIX and 

1450 self.mode & 0xF000 == 0xA000 

1451 ) 

1452 

1453 def is_file(self): 

1454 """Returns True if entry is a normal file.""" 

1455 return ( 

1456 self.type == RAR_BLOCK_FILE and 

1457 not (self.is_dir() or self.is_symlink()) 

1458 ) 

1459 

1460 

1461class RAR3Parser(CommonParser): 

1462 """Parse RAR3 file format. 

1463 """ 

1464 _expect_sig = RAR_ID 

1465 _last_aes_key = (None, None, None) # (salt, key, iv) 

1466 

1467 def _decrypt_header(self, fd): 

1468 if not _have_crypto: 

1469 raise NoCrypto("Cannot parse encrypted headers - no crypto") 

1470 salt = fd.read(8) 

1471 if self._last_aes_key[0] == salt: 

1472 key, iv = self._last_aes_key[1:] 

1473 else: 

1474 key, iv = rar3_s2k(self._password, salt) 

1475 self._last_aes_key = (salt, key, iv) 

1476 return HeaderDecrypt(fd, key, iv) 

1477 

1478 def _parse_block_header(self, fd): 

1479 """Parse common block header 

1480 """ 

1481 h = Rar3Info() 

1482 h.header_offset = fd.tell() 

1483 

1484 # read and parse base header 

1485 buf = fd.read(S_BLK_HDR.size) 

1486 if not buf: 

1487 return None 

1488 if len(buf) < S_BLK_HDR.size: 

1489 self._set_error("Unexpected EOF when reading header") 

1490 return None 

1491 t = S_BLK_HDR.unpack_from(buf) 

1492 h.header_crc, h.type, h.flags, h.header_size = t 

1493 

1494 # read full header 

1495 if h.header_size > S_BLK_HDR.size: 

1496 hdata = buf + fd.read(h.header_size - S_BLK_HDR.size) 

1497 else: 

1498 hdata = buf 

1499 h.data_offset = fd.tell() 

1500 

1501 # unexpected EOF? 

1502 if len(hdata) != h.header_size: 

1503 self._set_error("Unexpected EOF when reading header") 

1504 return None 

1505 

1506 pos = S_BLK_HDR.size 

1507 

1508 # block has data assiciated with it? 

1509 if h.flags & RAR_LONG_BLOCK: 

1510 h.add_size, pos = load_le32(hdata, pos) 

1511 else: 

1512 h.add_size = 0 

1513 

1514 # parse interesting ones, decide header boundaries for crc 

1515 if h.type == RAR_BLOCK_MARK: 

1516 return h 

1517 elif h.type == RAR_BLOCK_MAIN: 

1518 pos += 6 

1519 if h.flags & RAR_MAIN_ENCRYPTVER: 

1520 pos += 1 

1521 crc_pos = pos 

1522 if h.flags & RAR_MAIN_COMMENT: 

1523 self._parse_subblocks(h, hdata, pos) 

1524 elif h.type == RAR_BLOCK_FILE: 

1525 pos = self._parse_file_header(h, hdata, pos - 4) 

1526 crc_pos = pos 

1527 if h.flags & RAR_FILE_COMMENT: 

1528 pos = self._parse_subblocks(h, hdata, pos) 

1529 elif h.type == RAR_BLOCK_SUB: 

1530 pos = self._parse_file_header(h, hdata, pos - 4) 

1531 crc_pos = h.header_size 

1532 elif h.type == RAR_BLOCK_OLD_AUTH: 

1533 pos += 8 

1534 crc_pos = pos 

1535 elif h.type == RAR_BLOCK_OLD_EXTRA: 

1536 pos += 7 

1537 crc_pos = pos 

1538 elif h.type == RAR_BLOCK_OLD_SUB: 

1539 pos = self._parse_old_subblock(h, hdata, pos) 

1540 

1541 # these types do not have their own data CRC, 

1542 # so data was included in header CRC. 

1543 if h.old_sub_type in (RAR_OLD_SUB_UNIX, RAR_OLD_SUB_MAC): 

1544 # skip CRC check, it requires to read data part 

1545 return h 

1546 

1547 crc_pos = h.header_size 

1548 elif h.type == RAR_BLOCK_ENDARC: 

1549 if h.flags & RAR_ENDARC_DATACRC: 

1550 h.endarc_datacrc, pos = load_le32(hdata, pos) 

1551 if h.flags & RAR_ENDARC_VOLNR: 

1552 h.endarc_volnr = S_SHORT.unpack_from(hdata, pos)[0] 

1553 pos += 2 

1554 crc_pos = h.header_size 

1555 else: 

1556 crc_pos = h.header_size 

1557 

1558 # calculate crc 

1559 crcdat = hdata[2:crc_pos] 

1560 calc_crc = crc32(crcdat) & 0xFFFF 

1561 

1562 # return good header 

1563 if h.header_crc == calc_crc: 

1564 return h 

1565 

1566 # header parsing failed. 

1567 self._set_error("Header CRC error (%02x): exp=%x got=%x (xlen = %d)", 

1568 h.type, h.header_crc, calc_crc, len(crcdat)) 

1569 

1570 # instead panicing, send eof 

1571 return None 

1572 

1573 def _parse_file_header(self, h, hdata, pos): 

1574 """Read file-specific header 

1575 """ 

1576 fld = S_FILE_HDR.unpack_from(hdata, pos) 

1577 pos += S_FILE_HDR.size 

1578 

1579 h.compress_size = fld[0] 

1580 h.file_size = fld[1] 

1581 h.host_os = fld[2] 

1582 h.CRC = fld[3] 

1583 h.date_time = parse_dos_time(fld[4]) 

1584 h.mtime = to_datetime(h.date_time) 

1585 h.extract_version = fld[5] 

1586 h.compress_type = fld[6] 

1587 h._name_size = name_size = fld[7] 

1588 h.mode = fld[8] 

1589 

1590 h._md_class = CRC32Context 

1591 h._md_expect = h.CRC 

1592 

1593 if h.flags & RAR_FILE_LARGE: 

1594 h1, pos = load_le32(hdata, pos) 

1595 h2, pos = load_le32(hdata, pos) 

1596 h.compress_size |= h1 << 32 

1597 h.file_size |= h2 << 32 

1598 h.add_size = h.compress_size 

1599 

1600 name, pos = load_bytes(hdata, name_size, pos) 

1601 if h.flags & RAR_FILE_UNICODE and b"\0" in name: 

1602 # stored in custom encoding 

1603 nul = name.find(b"\0") 

1604 h.orig_filename = name[:nul] 

1605 u = UnicodeFilename(h.orig_filename, name[nul + 1:]) 

1606 h.filename = u.decode() 

1607 

1608 # if parsing failed fall back to simple name 

1609 if u.failed: 

1610 h.filename = self._decode(h.orig_filename) 

1611 elif h.flags & RAR_FILE_UNICODE: 

1612 # stored in UTF8 

1613 h.orig_filename = name 

1614 h.filename = name.decode("utf8", "replace") 

1615 else: 

1616 # stored in random encoding 

1617 h.orig_filename = name 

1618 h.filename = self._decode(name) 

1619 

1620 # change separator, set dir suffix 

1621 h.filename = h.filename.replace("\\", "/").rstrip("/") 

1622 if h.is_dir(): 

1623 h.filename = h.filename + "/" 

1624 

1625 if h.flags & RAR_FILE_SALT: 

1626 h.salt, pos = load_bytes(hdata, 8, pos) 

1627 else: 

1628 h.salt = None 

1629 

1630 # optional extended time stamps 

1631 if h.flags & RAR_FILE_EXTTIME: 

1632 pos = _parse_ext_time(h, hdata, pos) 

1633 else: 

1634 h.mtime = h.atime = h.ctime = h.arctime = None 

1635 

1636 return pos 

1637 

1638 def _parse_old_subblock(self, h, hdata, pos): 

1639 """Parse RAR2 subblock 

1640 """ 

1641 h.old_sub_type, _reserved = S_OLD_SUBBLOCK_HDR.unpack_from(hdata, pos) 

1642 return pos 

1643 

1644 def _parse_subblocks(self, h, hdata, pos): 

1645 """Find old-style comment subblock 

1646 """ 

1647 while pos < len(hdata): 

1648 # ordinary block header 

1649 t = S_BLK_HDR.unpack_from(hdata, pos) 

1650 ___scrc, stype, sflags, slen = t 

1651 pos_next = pos + slen 

1652 pos += S_BLK_HDR.size 

1653 

1654 # corrupt header 

1655 if pos_next < pos: 

1656 break 

1657 

1658 # followed by block-specific header 

1659 if stype == RAR_BLOCK_OLD_COMMENT and pos + S_COMMENT_HDR.size <= pos_next: 

1660 declen, ver, meth, crc = S_COMMENT_HDR.unpack_from(hdata, pos) 

1661 pos += S_COMMENT_HDR.size 

1662 data = hdata[pos: pos_next] 

1663 cmt = rar3_decompress(ver, meth, data, declen, sflags, 

1664 crc, self._password) 

1665 if not self._crc_check or (crc32(cmt) & 0xFFFF == crc): 

1666 h.comment = self._decode_comment(cmt) 

1667 

1668 pos = pos_next 

1669 return pos 

1670 

1671 def _read_comment_v3(self, inf, pwd=None): 

1672 

1673 # read data 

1674 with XFile(inf.volume_file) as rf: 

1675 rf.seek(inf.data_offset) 

1676 data = rf.read(inf.compress_size) 

1677 

1678 # decompress 

1679 cmt = rar3_decompress(inf.extract_version, inf.compress_type, data, 

1680 inf.file_size, inf.flags, inf.CRC, pwd, inf.salt) 

1681 

1682 # check crc 

1683 if self._crc_check: 

1684 crc = crc32(cmt) 

1685 if crc != inf.CRC: 

1686 return None 

1687 

1688 return self._decode_comment(cmt) 

1689 

1690 def _decode(self, val): 

1691 for c in TRY_ENCODINGS: 

1692 try: 

1693 return val.decode(c) 

1694 except UnicodeError: 

1695 pass 

1696 return val.decode(self._charset, "replace") 

1697 

1698 def _decode_comment(self, val): 

1699 return self._decode(val) 

1700 

1701 def process_entry(self, fd, item): 

1702 if item.type == RAR_BLOCK_FILE: 

1703 # use only first part 

1704 if item.flags & RAR_FILE_VERSION: 

1705 pass # skip old versions 

1706 elif (item.flags & RAR_FILE_SPLIT_BEFORE) == 0: 

1707 self._info_map[item.filename.rstrip("/")] = item 

1708 self._info_list.append(item) 

1709 elif len(self._info_list) > 0: 

1710 # final crc is in last block 

1711 old = self._info_list[-1] 

1712 old.CRC = item.CRC 

1713 old._md_expect = item._md_expect 

1714 old.compress_size += item.compress_size 

1715 

1716 # parse new-style comment 

1717 if item.type == RAR_BLOCK_SUB and item.filename == "CMT": 

1718 if item.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER): 

1719 pass 

1720 elif item.flags & RAR_FILE_SOLID: 

1721 # file comment 

1722 cmt = self._read_comment_v3(item, self._password) 

1723 if len(self._info_list) > 0: 

1724 old = self._info_list[-1] 

1725 old.comment = cmt 

1726 else: 

1727 # archive comment 

1728 cmt = self._read_comment_v3(item, self._password) 

1729 self.comment = cmt 

1730 

1731 if item.type == RAR_BLOCK_MAIN: 

1732 if item.flags & RAR_MAIN_COMMENT: 

1733 self.comment = item.comment 

1734 if item.flags & RAR_MAIN_PASSWORD: 

1735 self._needs_password = True 

1736 

1737 # put file compressed data into temporary .rar archive, and run 

1738 # unrar on that, thus avoiding unrar going over whole archive 

1739 def _open_hack(self, inf, pwd): 

1740 # create main header: crc, type, flags, size, res1, res2 

1741 prefix = RAR_ID + S_BLK_HDR.pack(0x90CF, 0x73, 0, 13) + b"\0" * (2 + 4) 

1742 return self._open_hack_core(inf, pwd, prefix, b"") 

1743 

1744 

1745# 

1746# RAR5 format 

1747# 

1748 

1749class Rar5Info(RarInfo): 

1750 """Shared fields for RAR5 records. 

1751 """ 

1752 extract_version = 50 

1753 header_crc = None 

1754 header_size = None 

1755 header_offset = None 

1756 data_offset = None 

1757 

1758 # type=all 

1759 block_type = None 

1760 block_flags = None 

1761 add_size = 0 

1762 block_extra_size = 0 

1763 

1764 # type=MAIN 

1765 volume_number = None 

1766 _md_class = None 

1767 _md_expect = None 

1768 

1769 def _must_disable_hack(self): 

1770 return False 

1771 

1772 

1773class Rar5BaseFile(Rar5Info): 

1774 """Shared sturct for file & service record. 

1775 """ 

1776 type = -1 

1777 file_flags = None 

1778 file_encryption = (0, 0, 0, b"", b"", b"") 

1779 file_compress_flags = None 

1780 file_redir = None 

1781 file_owner = None 

1782 file_version = None 

1783 blake2sp_hash = None 

1784 

1785 def _must_disable_hack(self): 

1786 if self.flags & RAR_FILE_PASSWORD: 

1787 return True 

1788 if self.block_flags & (RAR5_BLOCK_FLAG_SPLIT_BEFORE | RAR5_BLOCK_FLAG_SPLIT_AFTER): 

1789 return True 

1790 if self.file_compress_flags & RAR5_COMPR_SOLID: 

1791 return True 

1792 if self.file_redir: 

1793 return True 

1794 return False 

1795 

1796 

1797class Rar5FileInfo(Rar5BaseFile): 

1798 """RAR5 file record. 

1799 """ 

1800 type = RAR_BLOCK_FILE 

1801 

1802 def is_symlink(self): 

1803 """Returns True if entry is a symlink.""" 

1804 # pylint: disable=unsubscriptable-object 

1805 return ( 

1806 self.file_redir is not None and 

1807 self.file_redir[0] in ( 

1808 RAR5_XREDIR_UNIX_SYMLINK, 

1809 RAR5_XREDIR_WINDOWS_SYMLINK, 

1810 RAR5_XREDIR_WINDOWS_JUNCTION, 

1811 ) 

1812 ) 

1813 

1814 def is_file(self): 

1815 """Returns True if entry is a normal file.""" 

1816 return not (self.is_dir() or self.is_symlink()) 

1817 

1818 def is_dir(self): 

1819 """Returns True if entry is a directory.""" 

1820 if not self.file_redir: 

1821 if self.file_flags & RAR5_FILE_FLAG_ISDIR: 

1822 return True 

1823 return False 

1824 

1825 

1826class Rar5ServiceInfo(Rar5BaseFile): 

1827 """RAR5 service record. 

1828 """ 

1829 type = RAR_BLOCK_SUB 

1830 

1831 

1832class Rar5MainInfo(Rar5Info): 

1833 """RAR5 archive main record. 

1834 """ 

1835 type = RAR_BLOCK_MAIN 

1836 main_flags = None 

1837 main_volume_number = None 

1838 

1839 def _must_disable_hack(self): 

1840 if self.main_flags & RAR5_MAIN_FLAG_SOLID: 

1841 return True 

1842 return False 

1843 

1844 

1845class Rar5EncryptionInfo(Rar5Info): 

1846 """RAR5 archive header encryption record. 

1847 """ 

1848 type = RAR5_BLOCK_ENCRYPTION 

1849 encryption_algo = None 

1850 encryption_flags = None 

1851 encryption_kdf_count = None 

1852 encryption_salt = None 

1853 encryption_check_value = None 

1854 

1855 def needs_password(self): 

1856 return True 

1857 

1858 

1859class Rar5EndArcInfo(Rar5Info): 

1860 """RAR5 end of archive record. 

1861 """ 

1862 type = RAR_BLOCK_ENDARC 

1863 endarc_flags = None 

1864 

1865 

1866class RAR5Parser(CommonParser): 

1867 """Parse RAR5 format. 

1868 """ 

1869 _expect_sig = RAR5_ID 

1870 _hdrenc_main = None 

1871 

1872 # AES encrypted headers 

1873 _last_aes256_key = (-1, None, None) # (kdf_count, salt, key) 

1874 

1875 def _get_utf8_password(self): 

1876 pwd = self._password 

1877 if isinstance(pwd, str): 

1878 return pwd.encode("utf8") 

1879 return pwd 

1880 

1881 def _gen_key(self, kdf_count, salt): 

1882 if self._last_aes256_key[:2] == (kdf_count, salt): 

1883 return self._last_aes256_key[2] 

1884 if kdf_count > RAR_MAX_KDF_SHIFT: 

1885 raise BadRarFile("Too large kdf_count") 

1886 pwd = self._get_utf8_password() 

1887 key = rar5_s2k(pwd, salt, 1 << kdf_count) 

1888 self._last_aes256_key = (kdf_count, salt, key) 

1889 return key 

1890 

1891 def _decrypt_header(self, fd): 

1892 if not _have_crypto: 

1893 raise NoCrypto("Cannot parse encrypted headers - no crypto") 

1894 h = self._hdrenc_main 

1895 key = self._gen_key(h.encryption_kdf_count, h.encryption_salt) 

1896 iv = fd.read(16) 

1897 return HeaderDecrypt(fd, key, iv) 

1898 

1899 def _parse_block_header(self, fd): 

1900 """Parse common block header 

1901 """ 

1902 header_offset = fd.tell() 

1903 

1904 preload = 4 + 1 

1905 start_bytes = fd.read(preload) 

1906 if len(start_bytes) < preload: 

1907 self._set_error("Unexpected EOF when reading header") 

1908 return None 

1909 while start_bytes[-1] & 0x80: 

1910 b = fd.read(1) 

1911 if not b: 

1912 self._set_error("Unexpected EOF when reading header") 

1913 return None 

1914 start_bytes += b 

1915 header_crc, pos = load_le32(start_bytes, 0) 

1916 hdrlen, pos = load_vint(start_bytes, pos) 

1917 if hdrlen > 2 * 1024 * 1024: 

1918 return None 

1919 header_size = pos + hdrlen 

1920 

1921 # read full header, check for EOF 

1922 hdata = start_bytes + fd.read(header_size - len(start_bytes)) 

1923 if len(hdata) != header_size: 

1924 self._set_error("Unexpected EOF when reading header") 

1925 return None 

1926 data_offset = fd.tell() 

1927 

1928 calc_crc = crc32(memoryview(hdata)[4:]) 

1929 if header_crc != calc_crc: 

1930 # header parsing failed. 

1931 self._set_error("Header CRC error: exp=%x got=%x (xlen = %d)", 

1932 header_crc, calc_crc, len(hdata)) 

1933 return None 

1934 

1935 block_type, pos = load_vint(hdata, pos) 

1936 

1937 if block_type == RAR5_BLOCK_MAIN: 

1938 h, pos = self._parse_block_common(Rar5MainInfo(), hdata) 

1939 h = self._parse_main_block(h, hdata, pos) 

1940 elif block_type == RAR5_BLOCK_FILE: 

1941 h, pos = self._parse_block_common(Rar5FileInfo(), hdata) 

1942 h = self._parse_file_block(h, hdata, pos) 

1943 elif block_type == RAR5_BLOCK_SERVICE: 

1944 h, pos = self._parse_block_common(Rar5ServiceInfo(), hdata) 

1945 h = self._parse_file_block(h, hdata, pos) 

1946 elif block_type == RAR5_BLOCK_ENCRYPTION: 

1947 h, pos = self._parse_block_common(Rar5EncryptionInfo(), hdata) 

1948 h = self._parse_encryption_block(h, hdata, pos) 

1949 elif block_type == RAR5_BLOCK_ENDARC: 

1950 h, pos = self._parse_block_common(Rar5EndArcInfo(), hdata) 

1951 h = self._parse_endarc_block(h, hdata, pos) 

1952 else: 

1953 h = None 

1954 if h: 

1955 h.header_offset = header_offset 

1956 h.data_offset = data_offset 

1957 return h 

1958 

1959 def _parse_block_common(self, h, hdata): 

1960 h.header_crc, pos = load_le32(hdata, 0) 

1961 hdrlen, pos = load_vint(hdata, pos) 

1962 h.header_size = hdrlen + pos 

1963 h.block_type, pos = load_vint(hdata, pos) 

1964 h.block_flags, pos = load_vint(hdata, pos) 

1965 

1966 if h.block_flags & RAR5_BLOCK_FLAG_EXTRA_DATA: 

1967 h.block_extra_size, pos = load_vint(hdata, pos) 

1968 if h.block_flags & RAR5_BLOCK_FLAG_DATA_AREA: 

1969 h.add_size, pos = load_vint(hdata, pos) 

1970 

1971 h.compress_size = h.add_size 

1972 

1973 if h.block_flags & RAR5_BLOCK_FLAG_SKIP_IF_UNKNOWN: 

1974 h.flags |= RAR_SKIP_IF_UNKNOWN 

1975 if h.block_flags & RAR5_BLOCK_FLAG_DATA_AREA: 

1976 h.flags |= RAR_LONG_BLOCK 

1977 return h, pos 

1978 

1979 def _parse_main_block(self, h, hdata, pos): 

1980 h.main_flags, pos = load_vint(hdata, pos) 

1981 if h.main_flags & RAR5_MAIN_FLAG_HAS_VOLNR: 

1982 h.main_volume_number, pos = load_vint(hdata, pos) 

1983 

1984 h.flags |= RAR_MAIN_NEWNUMBERING 

1985 if h.main_flags & RAR5_MAIN_FLAG_SOLID: 

1986 h.flags |= RAR_MAIN_SOLID 

1987 if h.main_flags & RAR5_MAIN_FLAG_ISVOL: 

1988 h.flags |= RAR_MAIN_VOLUME 

1989 if h.main_flags & RAR5_MAIN_FLAG_RECOVERY: 

1990 h.flags |= RAR_MAIN_RECOVERY 

1991 if self._hdrenc_main: 

1992 h.flags |= RAR_MAIN_PASSWORD 

1993 if h.main_flags & RAR5_MAIN_FLAG_HAS_VOLNR == 0: 

1994 h.flags |= RAR_MAIN_FIRSTVOLUME 

1995 

1996 return h 

1997 

1998 def _parse_file_block(self, h, hdata, pos): 

1999 h.file_flags, pos = load_vint(hdata, pos) 

2000 h.file_size, pos = load_vint(hdata, pos) 

2001 h.mode, pos = load_vint(hdata, pos) 

2002 

2003 if h.file_flags & RAR5_FILE_FLAG_HAS_MTIME: 

2004 h.mtime, pos = load_unixtime(hdata, pos) 

2005 h.date_time = h.mtime.timetuple()[:6] 

2006 if h.file_flags & RAR5_FILE_FLAG_HAS_CRC32: 

2007 h.CRC, pos = load_le32(hdata, pos) 

2008 h._md_class = CRC32Context 

2009 h._md_expect = h.CRC 

2010 

2011 h.file_compress_flags, pos = load_vint(hdata, pos) 

2012 h.file_host_os, pos = load_vint(hdata, pos) 

2013 h.orig_filename, pos = load_vstr(hdata, pos) 

2014 h.filename = h.orig_filename.decode("utf8", "replace").rstrip("/") 

2015 

2016 # use compatible values 

2017 if h.file_host_os == RAR5_OS_WINDOWS: 

2018 h.host_os = RAR_OS_WIN32 

2019 else: 

2020 h.host_os = RAR_OS_UNIX 

2021 h.compress_type = RAR_M0 + ((h.file_compress_flags >> 7) & 7) 

2022 

2023 if h.block_extra_size: 

2024 # allow 1 byte of garbage 

2025 while pos < len(hdata) - 1: 

2026 xsize, pos = load_vint(hdata, pos) 

2027 xdata, pos = load_bytes(hdata, xsize, pos) 

2028 self._process_file_extra(h, xdata) 

2029 

2030 if h.block_flags & RAR5_BLOCK_FLAG_SPLIT_BEFORE: 

2031 h.flags |= RAR_FILE_SPLIT_BEFORE 

2032 if h.block_flags & RAR5_BLOCK_FLAG_SPLIT_AFTER: 

2033 h.flags |= RAR_FILE_SPLIT_AFTER 

2034 if h.file_flags & RAR5_FILE_FLAG_ISDIR: 

2035 h.flags |= RAR_FILE_DIRECTORY 

2036 if h.file_compress_flags & RAR5_COMPR_SOLID: 

2037 h.flags |= RAR_FILE_SOLID 

2038 

2039 if h.is_dir(): 

2040 h.filename = h.filename + "/" 

2041 return h 

2042 

2043 def _parse_endarc_block(self, h, hdata, pos): 

2044 h.endarc_flags, pos = load_vint(hdata, pos) 

2045 if h.endarc_flags & RAR5_ENDARC_FLAG_NEXT_VOL: 

2046 h.flags |= RAR_ENDARC_NEXT_VOLUME 

2047 return h 

2048 

2049 def _check_password(self, check_value, kdf_count_shift, salt): 

2050 if len(check_value) != RAR5_PW_CHECK_SIZE + RAR5_PW_SUM_SIZE: 

2051 return 

2052 if kdf_count_shift > RAR_MAX_KDF_SHIFT: 

2053 raise BadRarFile("Too large kdf_count") 

2054 

2055 hdr_check = check_value[:RAR5_PW_CHECK_SIZE] 

2056 hdr_sum = check_value[RAR5_PW_CHECK_SIZE:] 

2057 sum_hash = sha256(hdr_check).digest() 

2058 if sum_hash[:RAR5_PW_SUM_SIZE] != hdr_sum: 

2059 return 

2060 

2061 kdf_count = (1 << kdf_count_shift) + 32 

2062 pwd = self._get_utf8_password() 

2063 pwd_hash = rar5_s2k(pwd, salt, kdf_count) 

2064 

2065 pwd_check = bytearray(RAR5_PW_CHECK_SIZE) 

2066 len_mask = RAR5_PW_CHECK_SIZE - 1 

2067 for i, v in enumerate(pwd_hash): 

2068 pwd_check[i & len_mask] ^= v 

2069 

2070 if pwd_check != hdr_check: 

2071 raise RarWrongPassword() 

2072 

2073 def _parse_encryption_block(self, h, hdata, pos): 

2074 self._hdrenc_main = h 

2075 self._needs_password = True 

2076 h.encryption_algo, pos = load_vint(hdata, pos) 

2077 h.encryption_flags, pos = load_vint(hdata, pos) 

2078 h.encryption_kdf_count, pos = load_byte(hdata, pos) 

2079 h.encryption_salt, pos = load_bytes(hdata, 16, pos) 

2080 if h.encryption_flags & RAR5_ENC_FLAG_HAS_CHECKVAL: 

2081 h.encryption_check_value, pos = load_bytes(hdata, 12, pos) 

2082 if h.encryption_algo != RAR5_XENC_CIPHER_AES256: 

2083 raise BadRarFile("Unsupported header encryption cipher") 

2084 if h.encryption_check_value and self._password: 

2085 self._check_password(h.encryption_check_value, h.encryption_kdf_count, h.encryption_salt) 

2086 return h 

2087 

2088 def _process_file_extra(self, h, xdata): 

2089 xtype, pos = load_vint(xdata, 0) 

2090 if xtype == RAR5_XFILE_TIME: 

2091 self._parse_file_xtime(h, xdata, pos) 

2092 elif xtype == RAR5_XFILE_ENCRYPTION: 

2093 self._parse_file_encryption(h, xdata, pos) 

2094 elif xtype == RAR5_XFILE_HASH: 

2095 self._parse_file_hash(h, xdata, pos) 

2096 elif xtype == RAR5_XFILE_VERSION: 

2097 self._parse_file_version(h, xdata, pos) 

2098 elif xtype == RAR5_XFILE_REDIR: 

2099 self._parse_file_redir(h, xdata, pos) 

2100 elif xtype == RAR5_XFILE_OWNER: 

2101 self._parse_file_owner(h, xdata, pos) 

2102 elif xtype == RAR5_XFILE_SERVICE: 

2103 pass 

2104 else: 

2105 pass 

2106 

2107 # extra block for file time record 

2108 def _parse_file_xtime(self, h, xdata, pos): 

2109 tflags, pos = load_vint(xdata, pos) 

2110 

2111 ldr = load_windowstime 

2112 if tflags & RAR5_XTIME_UNIXTIME: 

2113 ldr = load_unixtime 

2114 

2115 if tflags & RAR5_XTIME_HAS_MTIME: 

2116 h.mtime, pos = ldr(xdata, pos) 

2117 h.date_time = h.mtime.timetuple()[:6] 

2118 if tflags & RAR5_XTIME_HAS_CTIME: 

2119 h.ctime, pos = ldr(xdata, pos) 

2120 if tflags & RAR5_XTIME_HAS_ATIME: 

2121 h.atime, pos = ldr(xdata, pos) 

2122 

2123 if tflags & RAR5_XTIME_UNIXTIME_NS: 

2124 if tflags & RAR5_XTIME_HAS_MTIME: 

2125 nsec, pos = load_le32(xdata, pos) 

2126 h.mtime = to_nsdatetime(h.mtime, nsec) 

2127 if tflags & RAR5_XTIME_HAS_CTIME: 

2128 nsec, pos = load_le32(xdata, pos) 

2129 h.ctime = to_nsdatetime(h.ctime, nsec) 

2130 if tflags & RAR5_XTIME_HAS_ATIME: 

2131 nsec, pos = load_le32(xdata, pos) 

2132 h.atime = to_nsdatetime(h.atime, nsec) 

2133 

2134 # just remember encryption info 

2135 def _parse_file_encryption(self, h, xdata, pos): 

2136 algo, pos = load_vint(xdata, pos) 

2137 flags, pos = load_vint(xdata, pos) 

2138 kdf_count, pos = load_byte(xdata, pos) 

2139 salt, pos = load_bytes(xdata, 16, pos) 

2140 iv, pos = load_bytes(xdata, 16, pos) 

2141 checkval = None 

2142 if flags & RAR5_XENC_CHECKVAL: 

2143 checkval, pos = load_bytes(xdata, 12, pos) 

2144 if flags & RAR5_XENC_TWEAKED: 

2145 h._md_expect = None 

2146 h._md_class = NoHashContext 

2147 

2148 h.file_encryption = (algo, flags, kdf_count, salt, iv, checkval) 

2149 h.flags |= RAR_FILE_PASSWORD 

2150 

2151 def _parse_file_hash(self, h, xdata, pos): 

2152 hash_type, pos = load_vint(xdata, pos) 

2153 if hash_type == RAR5_XHASH_BLAKE2SP: 

2154 h.blake2sp_hash, pos = load_bytes(xdata, 32, pos) 

2155 if (h.file_encryption[1] & RAR5_XENC_TWEAKED) == 0: 

2156 h._md_class = Blake2SP 

2157 h._md_expect = h.blake2sp_hash 

2158 

2159 def _parse_file_version(self, h, xdata, pos): 

2160 flags, pos = load_vint(xdata, pos) 

2161 version, pos = load_vint(xdata, pos) 

2162 h.file_version = (flags, version) 

2163 

2164 def _parse_file_redir(self, h, xdata, pos): 

2165 redir_type, pos = load_vint(xdata, pos) 

2166 redir_flags, pos = load_vint(xdata, pos) 

2167 redir_name, pos = load_vstr(xdata, pos) 

2168 redir_name = redir_name.decode("utf8", "replace") 

2169 h.file_redir = (redir_type, redir_flags, redir_name) 

2170 

2171 def _parse_file_owner(self, h, xdata, pos): 

2172 user_name = group_name = user_id = group_id = None 

2173 

2174 flags, pos = load_vint(xdata, pos) 

2175 if flags & RAR5_XOWNER_UNAME: 

2176 user_name, pos = load_vstr(xdata, pos) 

2177 if flags & RAR5_XOWNER_GNAME: 

2178 group_name, pos = load_vstr(xdata, pos) 

2179 if flags & RAR5_XOWNER_UID: 

2180 user_id, pos = load_vint(xdata, pos) 

2181 if flags & RAR5_XOWNER_GID: 

2182 group_id, pos = load_vint(xdata, pos) 

2183 

2184 h.file_owner = (user_name, group_name, user_id, group_id) 

2185 

2186 def process_entry(self, fd, item): 

2187 if item.block_type == RAR5_BLOCK_FILE: 

2188 if item.file_version: 

2189 pass # skip old versions 

2190 elif (item.block_flags & RAR5_BLOCK_FLAG_SPLIT_BEFORE) == 0: 

2191 # use only first part 

2192 self._info_map[item.filename.rstrip("/")] = item 

2193 self._info_list.append(item) 

2194 elif len(self._info_list) > 0: 

2195 # final crc is in last block 

2196 old = self._info_list[-1] 

2197 old.CRC = item.CRC 

2198 old._md_expect = item._md_expect 

2199 old.blake2sp_hash = item.blake2sp_hash 

2200 old.compress_size += item.compress_size 

2201 elif item.block_type == RAR5_BLOCK_SERVICE: 

2202 if item.filename == "CMT": 

2203 self._load_comment(fd, item) 

2204 

2205 def _load_comment(self, fd, item): 

2206 if item.block_flags & (RAR5_BLOCK_FLAG_SPLIT_BEFORE | RAR5_BLOCK_FLAG_SPLIT_AFTER): 

2207 return None 

2208 if item.compress_type != RAR_M0: 

2209 return None 

2210 

2211 if item.flags & RAR_FILE_PASSWORD: 

2212 algo, ___flags, kdf_count, salt, iv, ___checkval = item.file_encryption 

2213 if algo != RAR5_XENC_CIPHER_AES256: 

2214 return None 

2215 key = self._gen_key(kdf_count, salt) 

2216 f = HeaderDecrypt(fd, key, iv) 

2217 cmt = f.read(item.file_size) 

2218 else: 

2219 # archive comment 

2220 with self._open_clear(item) as cmtstream: 

2221 cmt = cmtstream.read() 

2222 

2223 # rar bug? - appends zero to comment 

2224 cmt = cmt.split(b"\0", 1)[0] 

2225 self.comment = cmt.decode("utf8") 

2226 return None 

2227 

2228 def _open_hack(self, inf, pwd): 

2229 # len, type, blk_flags, flags 

2230 main_hdr = b"\x03\x01\x00\x00" 

2231 endarc_hdr = b"\x03\x05\x00\x00" 

2232 main_hdr = S_LONG.pack(crc32(main_hdr)) + main_hdr 

2233 endarc_hdr = S_LONG.pack(crc32(endarc_hdr)) + endarc_hdr 

2234 return self._open_hack_core(inf, pwd, RAR5_ID + main_hdr, endarc_hdr) 

2235 

2236 

2237## 

2238## Utility classes 

2239## 

2240 

2241class UnicodeFilename: 

2242 """Handle RAR3 unicode filename decompression. 

2243 """ 

2244 def __init__(self, name, encdata): 

2245 self.std_name = bytearray(name) 

2246 self.encdata = bytearray(encdata) 

2247 self.pos = self.encpos = 0 

2248 self.buf = bytearray() 

2249 self.failed = 0 

2250 

2251 def enc_byte(self): 

2252 """Copy encoded byte.""" 

2253 try: 

2254 c = self.encdata[self.encpos] 

2255 self.encpos += 1 

2256 return c 

2257 except IndexError: 

2258 self.failed = 1 

2259 return 0 

2260 

2261 def std_byte(self): 

2262 """Copy byte from 8-bit representation.""" 

2263 try: 

2264 return self.std_name[self.pos] 

2265 except IndexError: 

2266 self.failed = 1 

2267 return ord("?") 

2268 

2269 def put(self, lo, hi): 

2270 """Copy 16-bit value to result.""" 

2271 self.buf.append(lo) 

2272 self.buf.append(hi) 

2273 self.pos += 1 

2274 

2275 def decode(self): 

2276 """Decompress compressed UTF16 value.""" 

2277 hi = self.enc_byte() 

2278 flagbits = 0 

2279 while self.encpos < len(self.encdata): 

2280 if flagbits == 0: 

2281 flags = self.enc_byte() 

2282 flagbits = 8 

2283 flagbits -= 2 

2284 t = (flags >> flagbits) & 3 

2285 if t == 0: 

2286 self.put(self.enc_byte(), 0) 

2287 elif t == 1: 

2288 self.put(self.enc_byte(), hi) 

2289 elif t == 2: 

2290 self.put(self.enc_byte(), self.enc_byte()) 

2291 else: 

2292 n = self.enc_byte() 

2293 if n & 0x80: 

2294 c = self.enc_byte() 

2295 for _ in range((n & 0x7f) + 2): 

2296 lo = (self.std_byte() + c) & 0xFF 

2297 self.put(lo, hi) 

2298 else: 

2299 for _ in range(n + 2): 

2300 self.put(self.std_byte(), 0) 

2301 return self.buf.decode("utf-16le", "replace") 

2302 

2303 

2304class RarExtFile(io.RawIOBase): 

2305 """Base class for file-like object that :meth:`RarFile.open` returns. 

2306 

2307 Provides public methods and common crc checking. 

2308 

2309 Behaviour: 

2310 - no short reads - .read() and .readinfo() read as much as requested. 

2311 - no internal buffer, use io.BufferedReader for that. 

2312 """ 

2313 name = None #: Filename of the archive entry 

2314 mode = "rb" 

2315 _parser = None 

2316 _inf = None 

2317 _fd = None 

2318 _remain = 0 

2319 _returncode = 0 

2320 _md_context = None 

2321 _seeking = False 

2322 

2323 def _open_extfile(self, parser, inf): 

2324 self.name = inf.filename 

2325 self._parser = parser 

2326 self._inf = inf 

2327 

2328 if self._fd: 

2329 self._fd.close() 

2330 if self._seeking: 

2331 md_class = NoHashContext 

2332 else: 

2333 md_class = self._inf._md_class or NoHashContext 

2334 self._md_context = md_class() 

2335 self._fd = None 

2336 self._remain = self._inf.file_size 

2337 

2338 def read(self, n=-1): 

2339 """Read all or specified amount of data from archive entry.""" 

2340 

2341 # sanitize count 

2342 if n is None or n < 0: 

2343 n = self._remain 

2344 elif n > self._remain: 

2345 n = self._remain 

2346 if n == 0: 

2347 return b"" 

2348 

2349 buf = [] 

2350 orig = n 

2351 while n > 0: 

2352 # actual read 

2353 data = self._read(n) 

2354 if not data: 

2355 break 

2356 buf.append(data) 

2357 self._md_context.update(data) 

2358 self._remain -= len(data) 

2359 n -= len(data) 

2360 data = b"".join(buf) 

2361 if n > 0: 

2362 if self._returncode: 

2363 check_returncode(self._returncode, "", tool_setup().get_errmap()) 

2364 raise BadRarFile("Failed the read enough data: req=%d got=%d" % (orig, len(data))) 

2365 

2366 # done? 

2367 if not data or self._remain == 0: 

2368 # self.close() 

2369 self._check() 

2370 return data 

2371 

2372 def _check(self): 

2373 """Check final CRC.""" 

2374 final = self._md_context.digest() 

2375 exp = self._inf._md_expect 

2376 if exp is None: 

2377 return 

2378 if final is None: 

2379 return 

2380 if self._returncode: 

2381 check_returncode(self._returncode, "", tool_setup().get_errmap()) 

2382 if self._remain != 0: 

2383 raise BadRarFile("Failed the read enough data") 

2384 if final != exp: 

2385 raise BadRarFile("Corrupt file - CRC check failed: %s - exp=%r got=%r" % ( 

2386 self._inf.filename, exp, final)) 

2387 

2388 def _read(self, cnt): 

2389 """Actual read that gets sanitized cnt.""" 

2390 raise NotImplementedError("_read") 

2391 

2392 def close(self): 

2393 """Close open resources.""" 

2394 

2395 super().close() 

2396 

2397 if self._fd: 

2398 self._fd.close() 

2399 self._fd = None 

2400 

2401 def __del__(self): 

2402 """Hook delete to make sure tempfile is removed.""" 

2403 self.close() 

2404 

2405 def readinto(self, buf): 

2406 """Zero-copy read directly into buffer. 

2407 

2408 Returns bytes read. 

2409 """ 

2410 raise NotImplementedError("readinto") 

2411 

2412 def tell(self): 

2413 """Return current reading position in uncompressed data.""" 

2414 return self._inf.file_size - self._remain 

2415 

2416 def seek(self, offset, whence=0): 

2417 """Seek in data. 

2418 

2419 On uncompressed files, the seeking works by actual 

2420 seeks so it's fast. On compressed files its slow 

2421 - forward seeking happens by reading ahead, 

2422 backwards by re-opening and decompressing from the start. 

2423 """ 

2424 

2425 # disable crc check when seeking 

2426 if not self._seeking: 

2427 self._md_context = NoHashContext() 

2428 self._seeking = True 

2429 

2430 fsize = self._inf.file_size 

2431 cur_ofs = self.tell() 

2432 

2433 if whence == 0: # seek from beginning of file 

2434 new_ofs = offset 

2435 elif whence == 1: # seek from current position 

2436 new_ofs = cur_ofs + offset 

2437 elif whence == 2: # seek from end of file 

2438 new_ofs = fsize + offset 

2439 else: 

2440 raise ValueError("Invalid value for whence") 

2441 

2442 # sanity check 

2443 if new_ofs < 0: 

2444 new_ofs = 0 

2445 elif new_ofs > fsize: 

2446 new_ofs = fsize 

2447 

2448 # do the actual seek 

2449 if new_ofs >= cur_ofs: 

2450 self._skip(new_ofs - cur_ofs) 

2451 else: 

2452 # reopen and seek 

2453 self._open_extfile(self._parser, self._inf) 

2454 self._skip(new_ofs) 

2455 return self.tell() 

2456 

2457 def _skip(self, cnt): 

2458 """Read and discard data""" 

2459 empty_read(self, cnt, BSIZE) 

2460 

2461 def readable(self): 

2462 """Returns True""" 

2463 return True 

2464 

2465 def writable(self): 

2466 """Returns False. 

2467 

2468 Writing is not supported. 

2469 """ 

2470 return False 

2471 

2472 def seekable(self): 

2473 """Returns True. 

2474 

2475 Seeking is supported, although it's slow on compressed files. 

2476 """ 

2477 return True 

2478 

2479 def readall(self): 

2480 """Read all remaining data""" 

2481 # avoid RawIOBase default impl 

2482 return self.read() 

2483 

2484 

2485class PipeReader(RarExtFile): 

2486 """Read data from pipe, handle tempfile cleanup.""" 

2487 

2488 def __init__(self, parser, inf, cmd, tempfile=None): 

2489 super().__init__() 

2490 self._cmd = cmd 

2491 self._proc = None 

2492 self._tempfile = tempfile 

2493 self._open_extfile(parser, inf) 

2494 

2495 def _close_proc(self): 

2496 if not self._proc: 

2497 return 

2498 for f in (self._proc.stdout, self._proc.stderr, self._proc.stdin): 

2499 if f: 

2500 f.close() 

2501 self._proc.wait() 

2502 self._returncode = self._proc.returncode 

2503 self._proc = None 

2504 

2505 def _open_extfile(self, parser, inf): 

2506 super()._open_extfile(parser, inf) 

2507 

2508 # stop old process 

2509 self._close_proc() 

2510 

2511 # launch new process 

2512 self._returncode = 0 

2513 self._proc = custom_popen(self._cmd) 

2514 self._fd = self._proc.stdout 

2515 

2516 def _read(self, cnt): 

2517 """Read from pipe.""" 

2518 

2519 # normal read is usually enough 

2520 data = self._fd.read(cnt) 

2521 if len(data) == cnt or not data: 

2522 return data 

2523 

2524 # short read, try looping 

2525 buf = [data] 

2526 cnt -= len(data) 

2527 while cnt > 0: 

2528 data = self._fd.read(cnt) 

2529 if not data: 

2530 break 

2531 cnt -= len(data) 

2532 buf.append(data) 

2533 return b"".join(buf) 

2534 

2535 def close(self): 

2536 """Close open resources.""" 

2537 

2538 self._close_proc() 

2539 super().close() 

2540 

2541 if self._tempfile: 

2542 try: 

2543 os.unlink(self._tempfile) 

2544 except OSError: 

2545 pass 

2546 self._tempfile = None 

2547 

2548 def readinto(self, buf): 

2549 """Zero-copy read directly into buffer.""" 

2550 cnt = len(buf) 

2551 if cnt > self._remain: 

2552 cnt = self._remain 

2553 vbuf = memoryview(buf) 

2554 res = got = 0 

2555 while got < cnt: 

2556 res = self._fd.readinto(vbuf[got: cnt]) 

2557 if not res: 

2558 break 

2559 self._md_context.update(vbuf[got: got + res]) 

2560 self._remain -= res 

2561 got += res 

2562 return got 

2563 

2564 

2565class DirectReader(RarExtFile): 

2566 """Read uncompressed data directly from archive. 

2567 """ 

2568 _cur = None 

2569 _cur_avail = None 

2570 _volfile = None 

2571 

2572 def __init__(self, parser, inf): 

2573 super().__init__() 

2574 self._open_extfile(parser, inf) 

2575 

2576 def _open_extfile(self, parser, inf): 

2577 super()._open_extfile(parser, inf) 

2578 

2579 self._volfile = self._inf.volume_file 

2580 self._fd = XFile(self._volfile, 0) 

2581 self._fd.seek(self._inf.header_offset, 0) 

2582 self._cur = self._parser._parse_header(self._fd) 

2583 self._cur_avail = self._cur.add_size 

2584 

2585 def _skip(self, cnt): 

2586 """RAR Seek, skipping through rar files to get to correct position 

2587 """ 

2588 

2589 while cnt > 0: 

2590 # next vol needed? 

2591 if self._cur_avail == 0: 

2592 if not self._open_next(): 

2593 break 

2594 

2595 # fd is in read pos, do the read 

2596 if cnt > self._cur_avail: 

2597 cnt -= self._cur_avail 

2598 self._remain -= self._cur_avail 

2599 self._cur_avail = 0 

2600 else: 

2601 self._fd.seek(cnt, 1) 

2602 self._cur_avail -= cnt 

2603 self._remain -= cnt 

2604 cnt = 0 

2605 

2606 def _read(self, cnt): 

2607 """Read from potentially multi-volume archive.""" 

2608 

2609 pos = self._fd.tell() 

2610 need = self._cur.data_offset + self._cur.add_size - self._cur_avail 

2611 if pos != need: 

2612 self._fd.seek(need, 0) 

2613 

2614 buf = [] 

2615 while cnt > 0: 

2616 # next vol needed? 

2617 if self._cur_avail == 0: 

2618 if not self._open_next(): 

2619 break 

2620 

2621 # fd is in read pos, do the read 

2622 if cnt > self._cur_avail: 

2623 data = self._fd.read(self._cur_avail) 

2624 else: 

2625 data = self._fd.read(cnt) 

2626 if not data: 

2627 break 

2628 

2629 # got some data 

2630 cnt -= len(data) 

2631 self._cur_avail -= len(data) 

2632 buf.append(data) 

2633 

2634 if len(buf) == 1: 

2635 return buf[0] 

2636 return b"".join(buf) 

2637 

2638 def _open_next(self): 

2639 """Proceed to next volume.""" 

2640 

2641 # is the file split over archives? 

2642 if (self._cur.flags & RAR_FILE_SPLIT_AFTER) == 0: 

2643 return False 

2644 

2645 if self._fd: 

2646 self._fd.close() 

2647 self._fd = None 

2648 

2649 # open next part 

2650 self._volfile = self._parser._next_volname(self._volfile) 

2651 fd = open(self._volfile, "rb", 0) 

2652 self._fd = fd 

2653 sig = fd.read(len(self._parser._expect_sig)) 

2654 if sig != self._parser._expect_sig: 

2655 raise BadRarFile("Invalid signature") 

2656 

2657 # loop until first file header 

2658 while True: 

2659 cur = self._parser._parse_header(fd) 

2660 if not cur: 

2661 raise BadRarFile("Unexpected EOF") 

2662 if cur.type in (RAR_BLOCK_MARK, RAR_BLOCK_MAIN): 

2663 if cur.add_size: 

2664 fd.seek(cur.add_size, 1) 

2665 continue 

2666 if cur.orig_filename != self._inf.orig_filename: 

2667 raise BadRarFile("Did not found file entry") 

2668 self._cur = cur 

2669 self._cur_avail = cur.add_size 

2670 return True 

2671 

2672 def readinto(self, buf): 

2673 """Zero-copy read directly into buffer.""" 

2674 got = 0 

2675 vbuf = memoryview(buf) 

2676 while got < len(buf): 

2677 # next vol needed? 

2678 if self._cur_avail == 0: 

2679 if not self._open_next(): 

2680 break 

2681 

2682 # length for next read 

2683 cnt = len(buf) - got 

2684 if cnt > self._cur_avail: 

2685 cnt = self._cur_avail 

2686 

2687 # read into temp view 

2688 res = self._fd.readinto(vbuf[got: got + cnt]) 

2689 if not res: 

2690 break 

2691 self._md_context.update(vbuf[got: got + res]) 

2692 self._cur_avail -= res 

2693 self._remain -= res 

2694 got += res 

2695 return got 

2696 

2697 

2698class HeaderDecrypt: 

2699 """File-like object that decrypts from another file""" 

2700 def __init__(self, f, key, iv): 

2701 self.f = f 

2702 self.ciph = AES_CBC_Decrypt(key, iv) 

2703 self.buf = b"" 

2704 

2705 def tell(self): 

2706 """Current file pos - works only on block boundaries.""" 

2707 return self.f.tell() 

2708 

2709 def read(self, cnt=None): 

2710 """Read and decrypt.""" 

2711 if cnt > 8 * 1024: 

2712 raise BadRarFile("Bad count to header decrypt - wrong password?") 

2713 

2714 # consume old data 

2715 if cnt <= len(self.buf): 

2716 res = self.buf[:cnt] 

2717 self.buf = self.buf[cnt:] 

2718 return res 

2719 res = self.buf 

2720 self.buf = b"" 

2721 cnt -= len(res) 

2722 

2723 # decrypt new data 

2724 blklen = 16 

2725 while cnt > 0: 

2726 enc = self.f.read(blklen) 

2727 if len(enc) < blklen: 

2728 break 

2729 dec = self.ciph.decrypt(enc) 

2730 if cnt >= len(dec): 

2731 res += dec 

2732 cnt -= len(dec) 

2733 else: 

2734 res += dec[:cnt] 

2735 self.buf = dec[cnt:] 

2736 cnt = 0 

2737 

2738 return res 

2739 

2740 

2741class XFile: 

2742 """Input may be filename or file object. 

2743 """ 

2744 __slots__ = ("_fd", "_need_close", "_initial_pos") 

2745 

2746 def __init__(self, xfile, bufsize=1024): 

2747 if is_filelike(xfile): 

2748 self._initial_pos = xfile.tell() 

2749 self._need_close = False 

2750 self._fd = xfile 

2751 self._fd.seek(0) 

2752 else: 

2753 self._initial_pos = None 

2754 self._need_close = True 

2755 self._fd = open(xfile, "rb", bufsize) 

2756 

2757 def restore_pos(self): 

2758 if self._initial_pos is None: 

2759 return 

2760 try: 

2761 self._fd.seek(self._initial_pos) 

2762 except: 

2763 pass 

2764 

2765 def read(self, n=None): 

2766 """Read from file.""" 

2767 return self._fd.read(n) 

2768 

2769 def tell(self): 

2770 """Return file pos.""" 

2771 return self._fd.tell() 

2772 

2773 def seek(self, ofs, whence=0): 

2774 """Move file pos.""" 

2775 return self._fd.seek(ofs, whence) 

2776 

2777 def readinto(self, buf): 

2778 """Read into buffer.""" 

2779 return self._fd.readinto(buf) 

2780 

2781 def close(self): 

2782 """Close file object.""" 

2783 if self._need_close: 

2784 self._fd.close() 

2785 

2786 def __enter__(self): 

2787 return self 

2788 

2789 def __exit__(self, typ, val, tb): 

2790 self.close() 

2791 

2792 

2793class NoHashContext: 

2794 """No-op hash function.""" 

2795 def __init__(self, data=None): 

2796 """Initialize""" 

2797 def update(self, data): 

2798 """Update data""" 

2799 def digest(self): 

2800 """Final hash""" 

2801 def hexdigest(self): 

2802 """Hexadecimal digest.""" 

2803 

2804 

2805class CRC32Context: 

2806 """Hash context that uses CRC32.""" 

2807 __slots__ = ["_crc"] 

2808 

2809 def __init__(self, data=None): 

2810 self._crc = 0 

2811 if data: 

2812 self.update(data) 

2813 

2814 def update(self, data): 

2815 """Process data.""" 

2816 self._crc = crc32(data, self._crc) 

2817 

2818 def digest(self): 

2819 """Final hash.""" 

2820 return self._crc 

2821 

2822 def hexdigest(self): 

2823 """Hexadecimal digest.""" 

2824 return "%08x" % self.digest() 

2825 

2826 

2827class Blake2SP: 

2828 """Blake2sp hash context. 

2829 """ 

2830 __slots__ = ["_thread", "_buf", "_cur", "_digest"] 

2831 digest_size = 32 

2832 block_size = 64 

2833 parallelism = 8 

2834 

2835 def __init__(self, data=None): 

2836 self._buf = b"" 

2837 self._cur = 0 

2838 self._digest = None 

2839 self._thread = [] 

2840 

2841 for i in range(self.parallelism): 

2842 ctx = self._blake2s(i, 0, i == (self.parallelism - 1)) 

2843 self._thread.append(ctx) 

2844 

2845 if data: 

2846 self.update(data) 

2847 

2848 def _blake2s(self, ofs, depth, is_last): 

2849 return blake2s(node_offset=ofs, node_depth=depth, last_node=is_last, 

2850 depth=2, inner_size=32, fanout=self.parallelism) 

2851 

2852 def _add_block(self, blk): 

2853 self._thread[self._cur].update(blk) 

2854 self._cur = (self._cur + 1) % self.parallelism 

2855 

2856 def update(self, data): 

2857 """Hash data. 

2858 """ 

2859 view = memoryview(data) 

2860 bs = self.block_size 

2861 if self._buf: 

2862 need = bs - len(self._buf) 

2863 if len(view) < need: 

2864 self._buf += view.tobytes() 

2865 return 

2866 self._add_block(self._buf + view[:need].tobytes()) 

2867 view = view[need:] 

2868 while len(view) >= bs: 

2869 self._add_block(view[:bs]) 

2870 view = view[bs:] 

2871 self._buf = view.tobytes() 

2872 

2873 def digest(self): 

2874 """Return final digest value. 

2875 """ 

2876 if self._digest is None: 

2877 if self._buf: 

2878 self._add_block(self._buf) 

2879 self._buf = b"" 

2880 ctx = self._blake2s(0, 1, True) 

2881 for t in self._thread: 

2882 ctx.update(t.digest()) 

2883 self._digest = ctx.digest() 

2884 return self._digest 

2885 

2886 def hexdigest(self): 

2887 """Hexadecimal digest.""" 

2888 return hexlify(self.digest()).decode("ascii") 

2889 

2890 

2891class Rar3Sha1: 

2892 """Emulate buggy SHA1 from RAR3. 

2893 """ 

2894 digest_size = 20 

2895 block_size = 64 

2896 

2897 _BLK_BE = struct.Struct(b">16L") 

2898 _BLK_LE = struct.Struct(b"<16L") 

2899 

2900 __slots__ = ("_nbytes", "_md", "_rarbug") 

2901 

2902 def __init__(self, data=b"", rarbug=False): 

2903 self._md = sha1() 

2904 self._nbytes = 0 

2905 self._rarbug = rarbug 

2906 self.update(data) 

2907 

2908 def update(self, data): 

2909 """Process more data.""" 

2910 self._md.update(data) 

2911 bufpos = self._nbytes & 63 

2912 self._nbytes += len(data) 

2913 

2914 if self._rarbug and len(data) > 64: 

2915 dpos = self.block_size - bufpos 

2916 while dpos + self.block_size <= len(data): 

2917 self._corrupt(data, dpos) 

2918 dpos += self.block_size 

2919 

2920 def digest(self): 

2921 """Return final state.""" 

2922 return self._md.digest() 

2923 

2924 def hexdigest(self): 

2925 """Return final state as hex string.""" 

2926 return self._md.hexdigest() 

2927 

2928 def _corrupt(self, data, dpos): 

2929 """Corruption from SHA1 core.""" 

2930 ws = list(self._BLK_BE.unpack_from(data, dpos)) 

2931 for t in range(16, 80): 

2932 tmp = ws[(t - 3) & 15] ^ ws[(t - 8) & 15] ^ ws[(t - 14) & 15] ^ ws[(t - 16) & 15] 

2933 ws[t & 15] = ((tmp << 1) | (tmp >> (32 - 1))) & 0xFFFFFFFF 

2934 self._BLK_LE.pack_into(data, dpos, *ws) 

2935 

2936 

2937## 

2938## Utility functions 

2939## 

2940 

2941S_LONG = Struct("<L") 

2942S_SHORT = Struct("<H") 

2943S_BYTE = Struct("<B") 

2944 

2945S_BLK_HDR = Struct("<HBHH") 

2946S_FILE_HDR = Struct("<LLBLLBBHL") 

2947S_COMMENT_HDR = Struct("<HBBH") 

2948S_OLD_SUBBLOCK_HDR = Struct("<HB") 

2949 

2950def load_vint(buf, pos): 

2951 """Load RAR5 variable-size int.""" 

2952 limit = min(pos + 11, len(buf)) 

2953 res = ofs = 0 

2954 while pos < limit: 

2955 b = buf[pos] 

2956 res += ((b & 0x7F) << ofs) 

2957 pos += 1 

2958 ofs += 7 

2959 if b < 0x80: 

2960 return res, pos 

2961 raise BadRarFile("cannot load vint") 

2962 

2963 

2964def load_byte(buf, pos): 

2965 """Load single byte""" 

2966 end = pos + 1 

2967 if end > len(buf): 

2968 raise BadRarFile("cannot load byte") 

2969 return S_BYTE.unpack_from(buf, pos)[0], end 

2970 

2971 

2972def load_le32(buf, pos): 

2973 """Load little-endian 32-bit integer""" 

2974 end = pos + 4 

2975 if end > len(buf): 

2976 raise BadRarFile("cannot load le32") 

2977 return S_LONG.unpack_from(buf, pos)[0], end 

2978 

2979 

2980def load_bytes(buf, num, pos): 

2981 """Load sequence of bytes""" 

2982 end = pos + num 

2983 if end > len(buf): 

2984 raise BadRarFile("cannot load bytes") 

2985 return buf[pos: end], end 

2986 

2987 

2988def load_vstr(buf, pos): 

2989 """Load bytes prefixed by vint length""" 

2990 slen, pos = load_vint(buf, pos) 

2991 return load_bytes(buf, slen, pos) 

2992 

2993 

2994def load_dostime(buf, pos): 

2995 """Load LE32 dos timestamp""" 

2996 stamp, pos = load_le32(buf, pos) 

2997 tup = parse_dos_time(stamp) 

2998 return to_datetime(tup), pos 

2999 

3000 

3001def load_unixtime(buf, pos): 

3002 """Load LE32 unix timestamp""" 

3003 secs, pos = load_le32(buf, pos) 

3004 dt = datetime.fromtimestamp(secs, timezone.utc) 

3005 return dt, pos 

3006 

3007 

3008def load_windowstime(buf, pos): 

3009 """Load LE64 windows timestamp""" 

3010 # unix epoch (1970) in seconds from windows epoch (1601) 

3011 unix_epoch = 11644473600 

3012 val1, pos = load_le32(buf, pos) 

3013 val2, pos = load_le32(buf, pos) 

3014 secs, n1secs = divmod((val2 << 32) | val1, 10000000) 

3015 dt = datetime.fromtimestamp(secs - unix_epoch, timezone.utc) 

3016 dt = to_nsdatetime(dt, n1secs * 100) 

3017 return dt, pos 

3018 

3019 

3020# 

3021# volume numbering 

3022# 

3023 

3024_rc_num = re.compile('^[0-9]+$') 

3025 

3026 

3027def _next_newvol(volfile): 

3028 """New-style next volume 

3029 """ 

3030 name, ext = os.path.splitext(volfile) 

3031 if ext.lower() in ("", ".exe", ".sfx"): 

3032 volfile = name + ".rar" 

3033 i = len(volfile) - 1 

3034 while i >= 0: 

3035 if "0" <= volfile[i] <= "9": 

3036 return _inc_volname(volfile, i, False) 

3037 if volfile[i] in ("/", os.sep): 

3038 break 

3039 i -= 1 

3040 raise BadRarName("Cannot construct volume name: " + volfile) 

3041 

3042 

3043 

3044def _next_oldvol(volfile): 

3045 """Old-style next volume 

3046 """ 

3047 name, ext = os.path.splitext(volfile) 

3048 if ext.lower() in ("", ".exe", ".sfx"): 

3049 ext = ".rar" 

3050 sfx = ext[2:] 

3051 if _rc_num.match(sfx): 

3052 ext = _inc_volname(ext, len(ext) - 1, True) 

3053 else: 

3054 # .rar -> .r00 

3055 ext = ext[:2] + "00" 

3056 return name + ext 

3057 

3058 

3059def _inc_volname(volfile, i, inc_chars): 

3060 """increase digits with carry, otherwise just increment char 

3061 """ 

3062 fn = list(volfile) 

3063 while i >= 0: 

3064 if fn[i] == "9": 

3065 fn[i] = "0" 

3066 i -= 1 

3067 if i < 0: 

3068 fn.insert(0, "1") 

3069 elif "0" <= fn[i] < "9" or inc_chars: 

3070 fn[i] = chr(ord(fn[i]) + 1) 

3071 break 

3072 else: 

3073 fn.insert(i + 1, "1") 

3074 break 

3075 return "".join(fn) 

3076 

3077 

3078def _parse_ext_time(h, data, pos): 

3079 """Parse all RAR3 extended time fields 

3080 """ 

3081 # flags and rest of data can be missing 

3082 flags = 0 

3083 if pos + 2 <= len(data): 

3084 flags = S_SHORT.unpack_from(data, pos)[0] 

3085 pos += 2 

3086 

3087 mtime, pos = _parse_xtime(flags >> 3 * 4, data, pos, h.mtime) 

3088 h.ctime, pos = _parse_xtime(flags >> 2 * 4, data, pos) 

3089 h.atime, pos = _parse_xtime(flags >> 1 * 4, data, pos) 

3090 h.arctime, pos = _parse_xtime(flags >> 0 * 4, data, pos) 

3091 if mtime: 

3092 h.mtime = mtime 

3093 h.date_time = mtime.timetuple()[:6] 

3094 return pos 

3095 

3096 

3097def _parse_xtime(flag, data, pos, basetime=None): 

3098 """Parse one RAR3 extended time field 

3099 """ 

3100 res = None 

3101 if flag & 8: 

3102 if not basetime: 

3103 basetime, pos = load_dostime(data, pos) 

3104 

3105 # load second fractions of 100ns units 

3106 rem = 0 

3107 cnt = flag & 3 

3108 for _ in range(cnt): 

3109 b, pos = load_byte(data, pos) 

3110 rem = (b << 16) | (rem >> 8) 

3111 

3112 # dostime has room for 30 seconds only, correct if needed 

3113 if flag & 4 and basetime.second < 59: 

3114 basetime = basetime.replace(second=basetime.second + 1) 

3115 

3116 res = to_nsdatetime(basetime, rem * 100) 

3117 return res, pos 

3118 

3119 

3120def is_filelike(obj): 

3121 """Filename or file object? 

3122 """ 

3123 if isinstance(obj, (bytes, str, Path)): 

3124 return False 

3125 res = True 

3126 for a in ("read", "tell", "seek"): 

3127 res = res and hasattr(obj, a) 

3128 if not res: 

3129 raise ValueError("Invalid object passed as file") 

3130 return True 

3131 

3132 

3133def rar5_s2k(pwd, salt, kdf_count): 

3134 """String-to-key hash for RAR5. 

3135 """ 

3136 if not isinstance(pwd, str): 

3137 pwd = pwd.decode("utf8") 

3138 wstr = pwd.encode("utf-16le")[:RAR_MAX_PASSWORD*2] 

3139 ustr = wstr.decode("utf-16le").encode("utf8") 

3140 return pbkdf2_hmac("sha256", ustr, salt, kdf_count) 

3141 

3142 

3143def rar3_s2k(pwd, salt): 

3144 """String-to-key hash for RAR3. 

3145 """ 

3146 if not isinstance(pwd, str): 

3147 pwd = pwd.decode("utf8") 

3148 wstr = pwd.encode("utf-16le")[:RAR_MAX_PASSWORD*2] 

3149 seed = bytearray(wstr + salt) 

3150 h = Rar3Sha1(rarbug=True) 

3151 iv = b"" 

3152 for i in range(16): 

3153 for j in range(0x4000): 

3154 cnt = S_LONG.pack(i * 0x4000 + j) 

3155 h.update(seed) 

3156 h.update(cnt[:3]) 

3157 if j == 0: 

3158 iv += h.digest()[19:20] 

3159 key_be = h.digest()[:16] 

3160 key_le = pack("<LLLL", *unpack(">LLLL", key_be)) 

3161 return key_le, iv 

3162 

3163 

3164def rar3_decompress(vers, meth, data, declen=0, flags=0, crc=0, pwd=None, salt=None): 

3165 """Decompress blob of compressed data. 

3166 

3167 Used for data with non-standard header - eg. comments. 

3168 """ 

3169 # already uncompressed? 

3170 if meth == RAR_M0 and (flags & RAR_FILE_PASSWORD) == 0: 

3171 return data 

3172 

3173 # take only necessary flags 

3174 flags = flags & (RAR_FILE_PASSWORD | RAR_FILE_SALT | RAR_FILE_DICTMASK) 

3175 flags |= RAR_LONG_BLOCK 

3176 

3177 # file header 

3178 fname = b"data" 

3179 date = ((2010 - 1980) << 25) + (12 << 21) + (31 << 16) 

3180 mode = DOS_MODE_ARCHIVE 

3181 fhdr = S_FILE_HDR.pack(len(data), declen, RAR_OS_MSDOS, crc, 

3182 date, vers, meth, len(fname), mode) 

3183 fhdr += fname 

3184 if salt: 

3185 fhdr += salt 

3186 

3187 # full header 

3188 hlen = S_BLK_HDR.size + len(fhdr) 

3189 hdr = S_BLK_HDR.pack(0, RAR_BLOCK_FILE, flags, hlen) + fhdr 

3190 hcrc = crc32(hdr[2:]) & 0xFFFF 

3191 hdr = S_BLK_HDR.pack(hcrc, RAR_BLOCK_FILE, flags, hlen) + fhdr 

3192 

3193 # archive main header 

3194 mh = S_BLK_HDR.pack(0x90CF, RAR_BLOCK_MAIN, 0, 13) + b"\0" * (2 + 4) 

3195 

3196 # decompress via temp rar 

3197 setup = tool_setup() 

3198 tmpfd, tmpname = mkstemp(suffix=".rar", dir=HACK_TMP_DIR) 

3199 tmpf = os.fdopen(tmpfd, "wb") 

3200 try: 

3201 tmpf.write(RAR_ID + mh + hdr + data) 

3202 tmpf.close() 

3203 

3204 curpwd = (flags & RAR_FILE_PASSWORD) and pwd or None 

3205 cmd = setup.open_cmdline(curpwd, tmpname) 

3206 p = custom_popen(cmd) 

3207 return p.communicate()[0] 

3208 finally: 

3209 tmpf.close() 

3210 os.unlink(tmpname) 

3211 

3212 

3213def sanitize_filename(fname, pathsep, is_win32): 

3214 """Make filename safe for write access. 

3215 """ 

3216 if is_win32: 

3217 if len(fname) > 1 and fname[1] == ":": 

3218 fname = fname[2:] 

3219 rc = RC_BAD_CHARS_WIN32 

3220 else: 

3221 rc = RC_BAD_CHARS_UNIX 

3222 if rc.search(fname): 

3223 fname = rc.sub("_", fname) 

3224 

3225 parts = [] 

3226 for seg in fname.split("/"): 

3227 if seg in ("", ".", ".."): 

3228 continue 

3229 if is_win32 and seg[-1] in (" ", "."): 

3230 seg = seg[:-1] + "_" 

3231 parts.append(seg) 

3232 return pathsep.join(parts) 

3233 

3234 

3235def empty_read(src, size, blklen): 

3236 """Read and drop fixed amount of data. 

3237 """ 

3238 while size > 0: 

3239 if size > blklen: 

3240 res = src.read(blklen) 

3241 else: 

3242 res = src.read(size) 

3243 if not res: 

3244 raise BadRarFile("cannot load data") 

3245 size -= len(res) 

3246 

3247 

3248def to_datetime(t): 

3249 """Convert 6-part time tuple into datetime object. 

3250 """ 

3251 # extract values 

3252 year, mon, day, h, m, s = t 

3253 

3254 # assume the values are valid 

3255 try: 

3256 return datetime(year, mon, day, h, m, s) 

3257 except ValueError: 

3258 pass 

3259 

3260 # sanitize invalid values 

3261 mday = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) 

3262 mon = max(1, min(mon, 12)) 

3263 day = max(1, min(day, mday[mon])) 

3264 h = min(h, 23) 

3265 m = min(m, 59) 

3266 s = min(s, 59) 

3267 return datetime(year, mon, day, h, m, s) 

3268 

3269 

3270def parse_dos_time(stamp): 

3271 """Parse standard 32-bit DOS timestamp. 

3272 """ 

3273 sec, stamp = stamp & 0x1F, stamp >> 5 

3274 mn, stamp = stamp & 0x3F, stamp >> 6 

3275 hr, stamp = stamp & 0x1F, stamp >> 5 

3276 day, stamp = stamp & 0x1F, stamp >> 5 

3277 mon, stamp = stamp & 0x0F, stamp >> 4 

3278 yr = (stamp & 0x7F) + 1980 

3279 return (yr, mon, day, hr, mn, sec * 2) 

3280 

3281 

3282# pylint: disable=arguments-differ,signature-differs 

3283class nsdatetime(datetime): 

3284 """Datetime that carries nanoseconds. 

3285 

3286 Arithmetic operations will lose nanoseconds. 

3287 

3288 .. versionadded:: 4.0 

3289 """ 

3290 __slots__ = ("nanosecond",) 

3291 nanosecond: int #: Number of nanoseconds, 0 <= nanosecond <= 999999999 

3292 

3293 def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0, 

3294 microsecond=0, tzinfo=None, *, fold=0, nanosecond=0): 

3295 usec, mod = divmod(nanosecond, 1000) if nanosecond else (microsecond, 0) 

3296 if mod == 0: 

3297 return datetime(year, month, day, hour, minute, second, usec, tzinfo, fold=fold) 

3298 self = super().__new__(cls, year, month, day, hour, minute, second, usec, tzinfo, fold=fold) 

3299 self.nanosecond = nanosecond 

3300 return self 

3301 

3302 def isoformat(self, sep="T", timespec="auto"): 

3303 """Formats with nanosecond precision by default. 

3304 """ 

3305 if timespec == "auto": 

3306 pre, post = super().isoformat(sep, "microseconds").split(".", 1) 

3307 return f"{pre}.{self.nanosecond:09d}{post[6:]}" 

3308 return super().isoformat(sep, timespec) 

3309 

3310 def astimezone(self, tz=None): 

3311 """Convert to new timezone. 

3312 """ 

3313 tmp = super().astimezone(tz) 

3314 return self.__class__(tmp.year, tmp.month, tmp.day, tmp.hour, tmp.minute, tmp.second, 

3315 nanosecond=self.nanosecond, tzinfo=tmp.tzinfo, fold=tmp.fold) 

3316 

3317 def replace(self, year=None, month=None, day=None, hour=None, minute=None, second=None, 

3318 microsecond=None, tzinfo=None, *, fold=None, nanosecond=None): 

3319 """Return new timestamp with specified fields replaced. 

3320 """ 

3321 return self.__class__( 

3322 self.year if year is None else year, 

3323 self.month if month is None else month, 

3324 self.day if day is None else day, 

3325 self.hour if hour is None else hour, 

3326 self.minute if minute is None else minute, 

3327 self.second if second is None else second, 

3328 nanosecond=((self.nanosecond if microsecond is None else microsecond * 1000) 

3329 if nanosecond is None else nanosecond), 

3330 tzinfo=self.tzinfo if tzinfo is None else tzinfo, 

3331 fold=self.fold if fold is None else fold) 

3332 

3333 def __hash__(self): 

3334 return hash((super().__hash__(), self.nanosecond)) if self.nanosecond else super().__hash__() 

3335 

3336 def __eq__(self, other): 

3337 return super().__eq__(other) and self.nanosecond == ( 

3338 other.nanosecond if isinstance(other, nsdatetime) else other.microsecond * 1000) 

3339 

3340 def __gt__(self, other): 

3341 return super().__gt__(other) or (super().__eq__(other) and self.nanosecond > ( 

3342 other.nanosecond if isinstance(other, nsdatetime) else other.microsecond * 1000)) 

3343 

3344 def __lt__(self, other): 

3345 return not (self > other or self == other) 

3346 

3347 def __ge__(self, other): 

3348 return not self < other 

3349 

3350 def __le__(self, other): 

3351 return not self > other 

3352 

3353 def __ne__(self, other): 

3354 return not self == other 

3355 

3356 

3357def to_nsdatetime(dt, nsec): 

3358 """Apply nanoseconds to datetime. 

3359 """ 

3360 if not nsec: 

3361 return dt 

3362 return nsdatetime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, 

3363 tzinfo=dt.tzinfo, fold=dt.fold, nanosecond=nsec) 

3364 

3365 

3366def to_nsecs(dt): 

3367 """Convert datatime instance to nanoseconds. 

3368 """ 

3369 secs = int(dt.timestamp()) 

3370 nsecs = dt.nanosecond if isinstance(dt, nsdatetime) else dt.microsecond * 1000 

3371 return secs * 1000000000 + nsecs 

3372 

3373 

3374def custom_popen(cmd): 

3375 """Disconnect cmd from parent fds, read only from stdout. 

3376 """ 

3377 creationflags = 0x08000000 if WIN32 else 0 # CREATE_NO_WINDOW 

3378 try: 

3379 p = Popen(cmd, bufsize=0, stdout=PIPE, stderr=STDOUT, stdin=DEVNULL, 

3380 creationflags=creationflags) 

3381 except OSError as ex: 

3382 if ex.errno == errno.ENOENT: 

3383 raise RarCannotExec("Unrar not installed?") from None 

3384 if ex.errno == errno.EACCES or ex.errno == errno.EPERM: 

3385 raise RarCannotExec("Cannot execute unrar") from None 

3386 raise 

3387 return p 

3388 

3389 

3390def check_returncode(code, out, errmap): 

3391 """Raise exception according to unrar exit code. 

3392 """ 

3393 if code == 0: 

3394 return 

3395 

3396 if code > 0 and code < len(errmap): 

3397 exc = errmap[code] 

3398 elif code == 255: 

3399 exc = RarUserBreak 

3400 elif code < 0: 

3401 exc = RarSignalExit 

3402 else: 

3403 exc = RarUnknownError 

3404 

3405 # format message 

3406 if out: 

3407 msg = "%s [%d]: %s" % (exc.__doc__, code, out) 

3408 else: 

3409 msg = "%s [%d]" % (exc.__doc__, code) 

3410 

3411 raise exc(msg) 

3412 

3413 

3414def membuf_tempfile(memfile): 

3415 """Write in-memory file object to real file. 

3416 """ 

3417 memfile.seek(0, 0) 

3418 

3419 tmpfd, tmpname = mkstemp(suffix=".rar", dir=HACK_TMP_DIR) 

3420 tmpf = os.fdopen(tmpfd, "wb") 

3421 

3422 try: 

3423 shutil.copyfileobj(memfile, tmpf, BSIZE) 

3424 tmpf.close() 

3425 except BaseException: 

3426 tmpf.close() 

3427 os.unlink(tmpname) 

3428 raise 

3429 return tmpname 

3430 

3431 

3432# 

3433# Find working command-line tool 

3434# 

3435 

3436class ToolSetup: 

3437 def __init__(self, setup): 

3438 self.setup = setup 

3439 

3440 def check(self): 

3441 cmdline = self.get_cmdline("check_cmd", None) 

3442 try: 

3443 p = custom_popen(cmdline) 

3444 out, _ = p.communicate() 

3445 return p.returncode == 0 

3446 except RarCannotExec: 

3447 return False 

3448 

3449 def open_cmdline(self, pwd, rarfn, filefn=None): 

3450 cmdline = self.get_cmdline("open_cmd", pwd) 

3451 cmdline.append(rarfn) 

3452 if filefn: 

3453 self.add_file_arg(cmdline, filefn) 

3454 return cmdline 

3455 

3456 def get_errmap(self): 

3457 return self.setup["errmap"] 

3458 

3459 def get_cmdline(self, key, pwd, nodash=False): 

3460 cmdline = list(self.setup[key]) 

3461 cmdline[0] = globals()[cmdline[0]] 

3462 if key == "check_cmd": 

3463 return cmdline 

3464 self.add_password_arg(cmdline, pwd) 

3465 if not nodash: 

3466 cmdline.append("--") 

3467 return cmdline 

3468 

3469 def add_file_arg(self, cmdline, filename): 

3470 cmdline.append(filename) 

3471 

3472 def add_password_arg(self, cmdline, pwd): 

3473 """Append password switch to commandline. 

3474 """ 

3475 if pwd is not None: 

3476 if not isinstance(pwd, str): 

3477 pwd = pwd.decode("utf8") 

3478 args = self.setup["password"] 

3479 if args is None: 

3480 tool = self.setup["open_cmd"][0] 

3481 raise RarCannotExec(f"{tool} does not support passwords") 

3482 elif isinstance(args, str): 

3483 cmdline.append(args + pwd) 

3484 else: 

3485 cmdline.extend(args) 

3486 cmdline.append(pwd) 

3487 else: 

3488 cmdline.extend(self.setup["no_password"]) 

3489 

3490 

3491UNRAR_CONFIG = { 

3492 "open_cmd": ("UNRAR_TOOL", "p", "-inul"), 

3493 "check_cmd": ("UNRAR_TOOL", "-inul", "-?"), 

3494 "password": "-p", 

3495 "no_password": ("-p-",), 

3496 # map return code to exception class, codes from rar.txt 

3497 "errmap": [None, 

3498 RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError, # 1..4 

3499 RarWriteError, RarOpenError, RarUserError, RarMemoryError, # 5..8 

3500 RarCreateError, RarNoFilesError, RarWrongPassword] # 9..11 

3501} 

3502 

3503# Problems with unar RAR backend: 

3504# - Does not support RAR2 locked files [fails to read] 

3505# - Does not support RAR5 Blake2sp hash [reading works] 

3506UNAR_CONFIG = { 

3507 "open_cmd": ("UNAR_TOOL", "-q", "-o", "-"), 

3508 "check_cmd": ("UNAR_TOOL", "-version"), 

3509 "password": ("-p",), 

3510 "no_password": ("-p", ""), 

3511 "errmap": [None], 

3512} 

3513 

3514# Problems with libarchive RAR backend: 

3515# - Does not support solid archives. 

3516# - Does not support password-protected archives. 

3517# - Does not support RARVM-based compression filters. 

3518BSDTAR_CONFIG = { 

3519 "open_cmd": ("BSDTAR_TOOL", "-x", "--to-stdout", "-f"), 

3520 "check_cmd": ("BSDTAR_TOOL", "--version"), 

3521 "password": None, 

3522 "no_password": (), 

3523 "errmap": [None], 

3524} 

3525 

3526SEVENZIP_CONFIG = { 

3527 "open_cmd": ("SEVENZIP_TOOL", "e", "-so", "-bb0"), 

3528 "check_cmd": ("SEVENZIP_TOOL", "i"), 

3529 "password": "-p", 

3530 "no_password": ("-p",), 

3531 "errmap": [None, 

3532 RarWarning, RarFatalError, None, None, # 1..4 

3533 None, None, RarUserError, RarMemoryError] # 5..8 

3534} 

3535 

3536SEVENZIP2_CONFIG = { 

3537 "open_cmd": ("SEVENZIP2_TOOL", "e", "-so", "-bb0"), 

3538 "check_cmd": ("SEVENZIP2_TOOL", "i"), 

3539 "password": "-p", 

3540 "no_password": ("-p",), 

3541 "errmap": [None, 

3542 RarWarning, RarFatalError, None, None, # 1..4 

3543 None, None, RarUserError, RarMemoryError] # 5..8 

3544} 

3545 

3546CURRENT_SETUP = None 

3547 

3548 

3549def tool_setup(unrar=True, unar=True, bsdtar=True, sevenzip=True, sevenzip2=True, force=False): 

3550 """Pick a tool, return cached ToolSetup. 

3551 """ 

3552 global CURRENT_SETUP 

3553 if force: 

3554 CURRENT_SETUP = None 

3555 if CURRENT_SETUP is not None: 

3556 return CURRENT_SETUP 

3557 lst = [] 

3558 if unrar: 

3559 lst.append(UNRAR_CONFIG) 

3560 if unar: 

3561 lst.append(UNAR_CONFIG) 

3562 if sevenzip: 

3563 lst.append(SEVENZIP_CONFIG) 

3564 if sevenzip2: 

3565 lst.append(SEVENZIP2_CONFIG) 

3566 if bsdtar: 

3567 lst.append(BSDTAR_CONFIG) 

3568 

3569 for conf in lst: 

3570 setup = ToolSetup(conf) 

3571 if setup.check(): 

3572 CURRENT_SETUP = setup 

3573 break 

3574 if CURRENT_SETUP is None: 

3575 raise RarCannotExec("Cannot find working tool") 

3576 return CURRENT_SETUP 

3577 

3578 

3579def main(args): 

3580 """Minimal command-line interface for rarfile module. 

3581 """ 

3582 import argparse 

3583 p = argparse.ArgumentParser(description=main.__doc__) 

3584 g = p.add_mutually_exclusive_group(required=True) 

3585 g.add_argument("-l", "--list", metavar="<rarfile>", 

3586 help="Show archive listing") 

3587 g.add_argument("-e", "--extract", nargs=2, 

3588 metavar=("<rarfile>", "<output_dir>"), 

3589 help="Extract archive into target dir") 

3590 g.add_argument("-t", "--test", metavar="<rarfile>", 

3591 help="Test if a archive is valid") 

3592 cmd = p.parse_args(args) 

3593 

3594 if cmd.list: 

3595 with RarFile(cmd.list) as rf: 

3596 rf.printdir() 

3597 elif cmd.test: 

3598 with RarFile(cmd.test) as rf: 

3599 rf.testrar() 

3600 elif cmd.extract: 

3601 with RarFile(cmd.extract[0]) as rf: 

3602 rf.extractall(cmd.extract[1]) 

3603 

3604 

3605if __name__ == "__main__": 

3606 main(sys.argv[1:]) 

3607