File Coverage

blib/lib/Parse/Win32Registry/Win95/File.pm
Criterion Covered Total %
statement 287 309 92.8
branch 62 88 70.4
condition 3 8 37.5
subroutine 49 52 94.2
pod 0 9 0.0
total 401 466 86.0


line stmt bran cond sub pod time code
1             package Parse::Win32Registry::Win95::File;
2              
3 13     13   96 use strict;
  13         29  
  13         418  
4 13     13   70 use warnings;
  13         25  
  13         392  
5              
6 13     13   66 use base qw(Parse::Win32Registry::File);
  13         24  
  13         5545  
7              
8 13     13   87 use Carp;
  13         27  
  13         667  
9 13     13   81 use File::Basename;
  13         27  
  13         1569  
10 13     13   97 use Parse::Win32Registry::Base qw(:all);
  13         26  
  13         3034  
11 13     13   5833 use Parse::Win32Registry::Win95::Key;
  13         37  
  13         450  
12              
13 13     13   88 use constant CREG_HEADER_LENGTH => 0x20;
  13         28  
  13         701  
14 13     13   112 use constant OFFSET_TO_RGKN_BLOCK => 0x20;
  13         30  
  13         13164  
15              
16             sub new {
17 22     22 0 7667 my $class = shift;
18 22 100       193 my $filename = shift or croak 'No filename specified';
19              
20 21 100       839 open my $fh, '<', $filename or croak "Unable to open '$filename': $!";
21              
22             # CREG Header
23             # 0x00 dword = 'CREG' signature
24             # 0x04
25             # 0x08 dword = offset to first rgdb block
26             # 0x0c
27             # 0x10 word = number of rgdb blocks
28              
29 20         189 my $bytes_read = sysread($fh, my $creg_header, CREG_HEADER_LENGTH);
30 20 100       101 if ($bytes_read != CREG_HEADER_LENGTH) {
31 1         5 warnf('Could not read registry file header');
32 1         26 return;
33             }
34              
35 19         147 my ($creg_sig,
36             $offset_to_first_rgdb_block,
37             $num_rgdb_blocks) = unpack('a4x4Vx4v', $creg_header);
38              
39 19 100       82 if ($creg_sig ne 'CREG') {
40 1         6 warnf('Invalid registry file signature');
41 1         27 return;
42             }
43              
44 18         43 my $self = {};
45 18         54 $self->{_filehandle} = $fh;
46 18         45 $self->{_filename} = $filename;
47 18         202 $self->{_length} = (stat $fh)[7];
48 18         64 $self->{_offset_to_first_rgdb_block} = $offset_to_first_rgdb_block;
49 18         42 $self->{_num_rgdb_blocks} = $num_rgdb_blocks;
50 18         76 bless $self, $class;
51              
52             # get_rgkn will cache the rgkn block for subsequent calls
53 18         66 my $rgkn_block = $self->get_rgkn;
54 18 100       135 return if !defined $rgkn_block; # warning will already have been made
55              
56             # Index the rgdb entries by id for faster look up
57 16         62 $self->_index_rgdb_entries;
58              
59 16         107 return $self;
60             }
61              
62             sub get_timestamp {
63 1     1 0 6 return undef;
64             }
65              
66             sub get_timestamp_as_string {
67 1     1 0 6 return iso8601(undef);
68             }
69              
70             sub get_embedded_filename {
71 1     1 0 6 return undef;
72             }
73              
74             sub get_root_key {
75 9     9 0 3995 my $self = shift;
76              
77 9         51 return $self->get_rgkn->get_root_key;
78             }
79              
80             sub get_virtual_root_key {
81 2     2 0 834 my $self = shift;
82 2         5 my $fake_root = shift;
83              
84 2         14 my $root_key = $self->get_root_key;
85 2 50       6 return if !defined $root_key;
86              
87 2 50       6 if (!defined $fake_root) {
88             # guess virtual root from filename
89 2         106 my $filename = basename $self->{_filename};
90              
91 2 100       15 if ($filename =~ /USER/i) {
    50          
92 1         2 $fake_root = 'HKEY_USERS';
93             }
94             elsif ($filename =~ /SYSTEM/i) {
95 1         3 $fake_root = 'HKEY_LOCAL_MACHINE';
96             }
97             else {
98 0         0 $fake_root = 'HKEY_UNKNOWN';
99             }
100             }
101              
102 2         13 $root_key->{_name} = $fake_root;
103 2         6 $root_key->{_key_path} = $fake_root;
104              
105 2         6 return $root_key;
106             }
107              
108             sub _index_rgdb_entries {
109 16     16   36 my $self = shift;
110              
111 16         33 my %index = ();
112              
113             # Build index of rgdb key entries
114             # Entries are only included if $key_block_num matches $rgdb_block_num
115 16         28 my $rgdb_block_num = 0;
116 16         58 my $rgdb_iter = $self->get_rgdb_iterator;
117 16         88 while (my $rgdb = $rgdb_iter->()) {
118 18         66 my $rgdb_key_iter = $rgdb->get_key_iterator;
119 18         56 while (my $rgdb_key = $rgdb_key_iter->()) {
120 88         149 my $key_id = $rgdb_key->{_id};
121 88         153 my $key_block_num = $key_id >> 16;
122 88 50       164 if ($rgdb_block_num == $key_block_num) {
123 88         267 $index{$key_id} = $rgdb_key;
124             }
125             }
126 18         272 $rgdb_block_num++;
127             }
128              
129 16         105 $self->{_rgdb_index} = \%index;
130             }
131              
132             sub _dump_rgdb_index {
133 0     0   0 my $self = shift;
134              
135 0         0 my $rgdb_index = $self->{_rgdb_index};
136              
137 0         0 foreach my $key_id (sort { $a <=> $b } keys %$rgdb_index) {
  0         0  
138 0         0 my $rgdb_key = $rgdb_index->{$key_id};
139             printf qq{id=0x%x 0x%x,%d/%d "%s" vals=%d\n},
140             $key_id,
141             $rgdb_key->{_offset},
142             $rgdb_key->{_length_used},
143             $rgdb_key->{_length},
144             $rgdb_key->{_name},
145 0         0 $rgdb_key->{_num_values};
146             }
147             }
148              
149             sub get_rgkn {
150 301     301 0 535 my $self = shift;
151              
152             # Return cached rgkn block if present
153 301 100       790 if (defined $self->{_rgkn}) {
154 283         734 return $self->{_rgkn};
155             }
156              
157 18         40 my $offset = OFFSET_TO_RGKN_BLOCK;
158 18         97 my $rgkn_block = Parse::Win32Registry::Win95::RGKN->new($self, $offset);
159 18         51 $self->{_rgkn} = $rgkn_block;
160 18         40 return $rgkn_block;
161             }
162              
163             sub get_rgdb_iterator {
164 18     18 0 32 my $self = shift;
165              
166 18         42 my $offset_to_next_rgdb_block = $self->{_offset_to_first_rgdb_block};
167 18         35 my $num_rgdb_blocks = $self->{_num_rgdb_blocks};
168              
169 18         32 my $end_of_file = $self->{_length};
170              
171 18         36 my $rgdb_block_num = 0;
172              
173             return Parse::Win32Registry::Iterator->new(sub {
174 38 100   38   119 if ($offset_to_next_rgdb_block > $end_of_file) {
175 5         13 return; # no more rgdb blocks
176             }
177 33 100       104 if ($rgdb_block_num >= $num_rgdb_blocks) {
178 13         58 return; # no more rgdb blocks
179             }
180 20         35 $rgdb_block_num++;
181 20 50       107 if (my $rgdb_block = Parse::Win32Registry::Win95::RGDB->new($self,
182             $offset_to_next_rgdb_block))
183             {
184 20 50       118 return unless $rgdb_block->get_length > 0;
185 20         68 $offset_to_next_rgdb_block += $rgdb_block->get_length;
186 20         83 return $rgdb_block;
187             }
188 18         181 });
189             }
190              
191             sub get_block_iterator {
192 2     2 0 4 my $self = shift;
193              
194 2         4 my $rgdb_iter;
195              
196             return Parse::Win32Registry::Iterator->new(sub {
197 6 100   6   24 if (!defined $rgdb_iter) {
198 2         7 $rgdb_iter = $self->get_rgdb_iterator;
199 2         7 return $self->get_rgkn;
200             }
201 4         11 return $rgdb_iter->();
202 2         16 });
203             }
204              
205             *get_hbin_iterator = \&get_block_iterator;
206              
207              
208             package Parse::Win32Registry::Win95::RGKN;
209              
210 13     13   124 use strict;
  13         48  
  13         356  
211 13     13   88 use warnings;
  13         44  
  13         558  
212              
213 13     13   115 use base qw(Parse::Win32Registry::Entry);
  13         29  
  13         1367  
214              
215 13     13   98 use Carp;
  13         24  
  13         788  
216 13     13   116 use Parse::Win32Registry::Base qw(:all);
  13         46  
  13         2670  
217              
218 13     13   96 use constant RGKN_HEADER_LENGTH => 0x20;
  13         27  
  13         772  
219 13     13   85 use constant OFFSET_TO_RGKN_BLOCK => 0x20;
  13         36  
  13         5148  
220              
221             sub new {
222 18     18   39 my $class = shift;
223 18         30 my $regfile = shift;
224 18   50     49 my $offset = shift || OFFSET_TO_RGKN_BLOCK;
225              
226 18 50       48 croak 'Missing registry file' if !defined $regfile;
227 18 50       46 croak 'Missing offset' if !defined $offset;
228              
229 18         88 my $fh = $regfile->get_filehandle;
230              
231             # RGKN Block Header
232             # 0x0 dword = 'RGKN' signature
233             # 0x4 dword = length of rgkn block
234             # 0x8 dword = offset to root key entry (relative to start of rgkn block)
235              
236 18         122 sysseek($fh, $offset, 0);
237 18         159 my $bytes_read = sysread($fh, my $rgkn_header, RGKN_HEADER_LENGTH);
238 18 100       76 if ($bytes_read != RGKN_HEADER_LENGTH) {
239 1         5 warnf('Could not read RGKN header at 0x%x', $offset);
240 1         8 return;
241             }
242              
243 17         85 my ($sig,
244             $rgkn_block_length,
245             $offset_to_root_key) = unpack('a4VV', $rgkn_header);
246              
247 17 100       60 if ($sig ne 'RGKN') {
248 1         5 warnf('Invalid RGKN block signature at 0x%x', $offset);
249 1         8 return;
250             }
251              
252 16         30 $offset_to_root_key += $offset;
253              
254 16         33 my $self = {};
255 16         48 $self->{_regfile} = $regfile;
256 16         32 $self->{_offset} = $offset;
257 16         34 $self->{_length} = $rgkn_block_length;
258 16         41 $self->{_header_length} = RGKN_HEADER_LENGTH;
259 16         39 $self->{_allocated} = 1;
260 16         36 $self->{_tag} = 'rgkn block';
261 16         36 $self->{_offset_to_root_key} = $offset_to_root_key;
262 16         34 bless $self, $class;
263              
264 16         45 return $self;
265             }
266              
267             sub get_root_key {
268 11     11   24 my $self = shift;
269              
270 11         51 my $regfile = $self->{_regfile};
271 11         28 my $offset_to_root_key = $self->{_offset_to_root_key};
272              
273 11         83 my $root_key = Parse::Win32Registry::Win95::Key->new($regfile,
274             $offset_to_root_key);
275 11         51 return $root_key;
276             }
277              
278             sub get_entry_iterator {
279 2     2   4 my $self = shift;
280              
281 2         16 my $root_key = $self->get_root_key;
282              
283             # In the unlikely event there is no root key, return an empty iterator
284 2 50       8 if (defined $root_key) {
285 2         24 return $root_key->get_subtree_iterator;
286             }
287             else {
288 0     0   0 return Parse::Win32Registry::Iterator->new(sub {});
289             }
290             }
291              
292              
293             package Parse::Win32Registry::Win95::RGDB;
294              
295 13     13   107 use base qw(Parse::Win32Registry::Entry);
  13         42  
  13         1374  
296              
297 13     13   95 use Carp;
  13         29  
  13         816  
298 13     13   101 use Parse::Win32Registry::Base qw(:all);
  13         29  
  13         2725  
299              
300 13     13   101 use constant RGDB_HEADER_LENGTH => 0x20;
  13         50  
  13         7017  
301              
302             sub new {
303 20     20   48 my $class = shift;
304 20         33 my $regfile = shift;
305 20         37 my $offset = shift;
306              
307 20 50       56 croak 'Missing registry file' if !defined $regfile;
308 20 50       69 croak 'Missing offset' if !defined $offset;
309              
310 20         75 my $fh = $regfile->get_filehandle;
311              
312             # RGDB Block Header
313             # 0x0 dword = 'RDGB' signature
314             # 0x4 dword = length of rgdb block
315              
316 20         142 sysseek($fh, $offset, 0);
317 20         185 my $bytes_read = sysread($fh, my $rgdb_header, RGDB_HEADER_LENGTH);
318 20 50       89 if ($bytes_read != RGDB_HEADER_LENGTH) {
319 0         0 return;
320             }
321              
322 20         99 my ($sig,
323             $rgdb_block_length) = unpack('a4V', $rgdb_header);
324              
325 20 50       84 if ($sig ne 'RGDB') {
326 0         0 return;
327             }
328              
329 20         40 my $self = {};
330 20         54 $self->{_regfile} = $regfile;
331 20         45 $self->{_offset} = $offset;
332 20         43 $self->{_length} = $rgdb_block_length;
333 20         38 $self->{_header_length} = RGDB_HEADER_LENGTH;
334 20         47 $self->{_allocated} = 1;
335 20         50 $self->{_tag} = 'rgdb block';
336 20         46 bless $self, $class;
337              
338 20         162 return $self;
339             }
340              
341             sub get_key_iterator {
342 20     20   40 my $self = shift;
343              
344 20         47 my $regfile = $self->{_regfile};
345 20         35 my $offset = $self->{_offset};
346 20         40 my $length = $self->{_length};
347              
348 20         37 my $offset_to_next_rgdb_key = $offset + RGDB_HEADER_LENGTH;
349 20         38 my $end_of_rgdb_block = $offset + $length;
350              
351             return Parse::Win32Registry::Iterator->new(sub {
352 112 100   112   238 if ($offset_to_next_rgdb_key >= $end_of_rgdb_block) {
353 20         56 return;
354             }
355 92 50       263 if (my $rgdb_key = Parse::Win32Registry::Win95::RGDBKey->new($regfile,
356             $offset_to_next_rgdb_key))
357             {
358 92 50       315 return unless $rgdb_key->get_length > 0;
359 92         284 $offset_to_next_rgdb_key += $rgdb_key->get_length;
360              
361             # Check rgdb key has not run past end of rgdb block
362 92 50       198 if ($offset_to_next_rgdb_key > $end_of_rgdb_block) {
363 0         0 return;
364             }
365 92         252 return $rgdb_key;
366             }
367 20         137 });
368             }
369              
370             sub get_entry_iterator {
371 2     2   5 my $self = shift;
372              
373 2         5 my $value_iter;
374 2         8 my $key_iter = $self->get_key_iterator;
375              
376             return Parse::Win32Registry::Iterator->new(sub {
377 14 100   14   33 if (defined $value_iter) {
378 12         26 my $value = $value_iter->();
379 12 100       30 if (defined $value) {
380 8         23 return $value;
381             }
382             }
383              
384 6         16 my $key = $key_iter->();
385 6 100       20 if (!defined $key) {
386 2         6 return; # key iterator finished
387             }
388              
389 4         11 $value_iter = $key->get_value_iterator;
390 4         10 return $key;
391 2         11 });
392             }
393              
394              
395             package Parse::Win32Registry::Win95::RGDBKey;
396              
397 13     13   109 use base qw(Parse::Win32Registry::Entry);
  13         83  
  13         1408  
398              
399 13     13   99 use Carp;
  13         24  
  13         849  
400 13     13   111 use Encode;
  13         26  
  13         1013  
401 13     13   91 use Parse::Win32Registry::Base qw(:all);
  13         28  
  13         2856  
402              
403 13     13   101 use constant RGDB_ENTRY_HEADER_LENGTH => 0x14;
  13         35  
  13         8418  
404              
405             sub new {
406 92     92   170 my $class = shift;
407 92         147 my $regfile = shift;
408 92         146 my $offset = shift;
409              
410 92 50       233 croak 'Missing registry file' if !defined $regfile;
411 92 50       177 croak 'Missing offset' if !defined $offset;
412              
413 92         247 my $fh = $regfile->get_filehandle;
414              
415             # RGDB Key Entry
416             # 0x00 dword = length of rgdb entry / offset to next rgdb entry
417             # (this length includes any following value entries)
418             # 0x04 dword = id (top word = block num, bottom word = id)
419             # 0x08 dword = bytes used (unpacked, but not used)
420             # 0x0c word = key name length
421             # 0x0e word = number of values
422             # 0x10 dword
423             # 0x14 = key name [for key name length bytes]
424             # followed immediately by any RGDB Value Entries belonging to this key
425              
426 92         560 sysseek($fh, $offset, 0);
427 92         721 my $bytes_read = sysread($fh, my $rgdb_key_entry, RGDB_ENTRY_HEADER_LENGTH);
428 92 50       317 if ($bytes_read != RGDB_ENTRY_HEADER_LENGTH) {
429 0         0 return;
430             }
431              
432 92         362 my ($length,
433             $key_id,
434             $length_used,
435             $name_length,
436             $num_values) = unpack('VVVvv', $rgdb_key_entry);
437              
438 92         568 $bytes_read = sysread($fh, my $name, $name_length);
439 92 50       319 if ($bytes_read != $name_length) {
440 0         0 return;
441             }
442 92         309 $name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
443              
444             # Calculate the length of the entry's key header
445 92         3145 my $header_length = RGDB_ENTRY_HEADER_LENGTH + $name_length;
446              
447             # Check for invalid/unused entries
448 92 50 33     474 if ($key_id == 0xffffffff || $length_used == 0xffffffff
      33        
449             || $header_length > $length)
450             {
451 0         0 $name = '';
452 0         0 $header_length = RGDB_ENTRY_HEADER_LENGTH;
453             }
454              
455 92         170 my $self = {};
456 92         214 $self->{_regfile} = $regfile;
457 92         175 $self->{_offset} = $offset;
458 92         172 $self->{_length} = $length;
459 92         151 $self->{_length_used} = $length_used;
460 92         155 $self->{_header_length} = $header_length;
461 92         172 $self->{_allocated} = 1;
462 92         186 $self->{_tag} = 'rgdb key';
463 92         243 $self->{_id} = $key_id;
464 92         180 $self->{_name} = $name;
465 92         156 $self->{_name_length} = $name_length;
466 92         148 $self->{_num_values} = $num_values;
467 92         168 bless $self, $class;
468              
469 92         372 return $self;
470             }
471              
472             sub get_name {
473 299     299   460 my $self = shift;
474              
475 299         928 return $self->{_name};
476             }
477              
478             sub parse_info {
479 0     0   0 my $self = shift;
480              
481             my $info = sprintf '0x%x rgdb key len=0x%x/0x%x "%s" id=0x%x vals=%d',
482             $self->{_offset},
483             $self->{_length_used},
484             $self->{_length},
485             $self->{_name},
486             $self->{_id},
487 0         0 $self->{_num_values};
488              
489 0         0 return $info;
490             }
491              
492             sub get_value_iterator {
493 144     144   229 my $self = shift;
494              
495 144         259 my $regfile = $self->{_regfile};
496              
497 144         253 my $num_values_remaining = $self->{_num_values};
498              
499 144         247 my $offset = $self->{_offset};
500              
501             # offset_to_next_rgdb_value can only be set to a valid offset
502             # if num_values_remaining > 0
503 144         232 my $offset_to_next_rgdb_value = 0xffffffff;
504 144 100       355 if ($num_values_remaining > 0) {
505             $offset_to_next_rgdb_value = $offset
506 119         210 + $self->{_header_length};
507             }
508              
509 144         275 my $end_of_rgdb_key = $offset + $self->{_length};
510              
511             # don't attempt to return values if id is invalid...
512 144 50       383 if ($self->{_id} == 0xffffffff) {
513 0         0 $num_values_remaining = 0;
514             }
515              
516             return Parse::Win32Registry::Iterator->new(sub {
517 3321 100   3321   7156 if ($num_values_remaining-- <= 0) {
518 134         377 return;
519             }
520 3187 50       6162 if ($offset_to_next_rgdb_value == 0xffffffff) {
521 0         0 return;
522             }
523 3187 50       5902 if ($offset_to_next_rgdb_value > $end_of_rgdb_key) {
524 0         0 return;
525             }
526 3187 50       7520 if (my $value = Parse::Win32Registry::Win95::Value->new($regfile,
527             $offset_to_next_rgdb_value))
528             {
529 3187 50       8991 return unless $value->get_length > 0;
530 3187         6496 $offset_to_next_rgdb_value += $value->get_length;
531 3187         9390 return $value;
532             }
533             else {
534 0           return; # no more values
535             }
536 144         916 });
537             }
538              
539             1;
540