[PAR2PARSER] Add extra corrupt file checks
[rarslave2.git] / par2parser.py
1 #!/usr/bin/env python
2 # vim: set ts=4 sts=4 sw=4 textwidth=92:
3
4 ################################################################################
5 # The PAR2 Parser
6 #
7 # This was stolen from cfv (see http://cfv.sourceforge.net/ for a copy)
8 ################################################################################
9
10 import struct, errno, os
11
12 def get_full_filename (dir, file):
13         return os.path.abspath (os.path.expanduser (os.path.join (dir, file)))
14
15 def chompnulls(line):
16         p = line.find('\0')
17         if p < 0: return line
18         else:     return line[:p]
19
20 def get_protected_files (dir, filename):
21         """Get all of the filenames that are protected by the par2
22         file given as the filename"""
23
24         full_filename = get_full_filename (dir, filename)
25
26         try:
27                 file = open(full_filename, 'rb')
28         except:
29                 print 'Could not open %s' % (full_filename, )
30                 raise EnvironmentError, (errno.EINVAL, 'can not open par2 file')
31
32         pkt_header_fmt = '< 8s Q 16s 16s 16s'
33         pkt_header_size = struct.calcsize(pkt_header_fmt)
34         file_pkt_fmt = '< 16s 16s 16s Q'
35         file_pkt_size = struct.calcsize(file_pkt_fmt)
36         main_pkt_fmt = '< Q I'
37         main_pkt_size = struct.calcsize(main_pkt_fmt)
38
39         seen_file_ids = {}
40         expected_file_ids = None
41         filenames = []
42
43         while 1:
44                 try:
45                         d = file.read(pkt_header_size)
46                 except OverflowError:
47                         raise EnvironmentError, (errno.EINVAL, 'bad par2 file')
48                 if not d:
49                         break
50
51                 magic, pkt_len, pkt_md5, set_id, pkt_type = struct.unpack(pkt_header_fmt, d)
52
53                 if pkt_type == 'PAR 2.0\0FileDesc':
54                         file_id, file_md5, file_md5_16k, file_size = \
55                                 struct.unpack(file_pkt_fmt, d[:file_pkt_size])
56
57                         if seen_file_ids.get(file_id) is None:
58                                 seen_file_ids[file_id] = 1
59                                 filename = chompnulls(d[file_pkt_size:])
60                                 filenames.append(filename)
61
62                 elif pkt_type == "PAR 2.0\0Main\0\0\0\0":
63                         try:
64                                 d = file.read(pkt_len - pkt_header_size)
65                         except OverflowError:
66                                 raise EnvironmentError, (errno.EINVAL, 'corrupt par2 file')
67
68                         if expected_file_ids is None:
69                                 expected_file_ids = []
70                                 slice_size, num_files = struct.unpack(main_pkt_fmt, d[:main_pkt_size])
71                                 num_nonrecovery = (len(d)-main_pkt_size)/16 - num_files
72
73                                 for i in range(main_pkt_size,main_pkt_size+(num_files+num_nonrecovery)*16,16):
74                                         expected_file_ids.append(d[i:i+16])
75
76                 else:
77                         try:
78                                 file.seek(pkt_len - pkt_header_size, 1)
79                         except OverflowError, IOError:
80                                 raise EnvironmentError, (errno.EINVAL, 'corrupt par2 file')
81
82         if expected_file_ids is None:
83                 raise EnvironmentError, (errno.EINVAL, \
84                         "corrupt or unsupported par2 file - no main packet found")
85
86         for id in expected_file_ids:
87                 if not seen_file_ids.has_key(id):
88                         raise EnvironmentError, (errno.EINVAL, \
89                                 "corrupt or unsupported par2 file - " \
90                                 "expected file description packet not found")
91
92         return filenames
93
94 def main ():
95         pass
96
97 if __name__ == '__main__':
98         main ()
99