1"""RAR handler.
2
3RAR Version 4.x
4https://codedread.github.io/bitjs/docs/unrar.html.
5
6RAR Version 5.x
7https://www.rarlab.com/technote.htm#rarsign
8"""
9
10from typing import Optional
11
12import rarfile
13from structlog import get_logger
14
15from unblob.extractors import Command
16
17from ...models import (
18 File,
19 Handler,
20 HandlerDoc,
21 HandlerType,
22 HexString,
23 Reference,
24 ValidChunk,
25)
26
27logger = get_logger()
28
29
30class RarHandler(Handler):
31 NAME = "rar"
32
33 PATTERNS = [
34 HexString(
35 """
36 // RAR v4.x ends with 00, RAR v5.x ends with 01 00
37 52 61 72 21 1A 07 ( 00 | 01 00 )
38 """
39 )
40 ]
41 EXTRACTOR = Command("unar", "-no-directory", "-p", "", "{inpath}", "-o", "{outdir}")
42
43 DOC = HandlerDoc(
44 name="RAR",
45 description="RAR archive files are commonly used for compressed data storage. They can contain multiple files and directories, and support various compression methods.",
46 handler_type=HandlerType.ARCHIVE,
47 vendor=None,
48 references=[
49 Reference(
50 title="RAR 4.x File Format Documentation",
51 url="https://codedread.github.io/bitjs/docs/unrar.html",
52 ),
53 Reference(
54 title="RAR 5.x File Format Documentation",
55 url="https://www.rarlab.com/technote.htm#rarsign",
56 ),
57 ],
58 limitations=["Does not support encrypted RAR files."],
59 )
60
61 def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]:
62 try:
63 rar_file = rarfile.RarFile(file)
64 except (rarfile.Error, ValueError):
65 return None
66
67 # RarFile has the side effect of moving the file pointer
68 rar_end_offset = file.tell()
69
70 return ValidChunk(
71 start_offset=start_offset,
72 end_offset=rar_end_offset,
73 is_encrypted=rar_file.needs_password(),
74 )