| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Email::Outlook::Message::Base; | 
| 2 |  |  |  |  |  |  | =head1 NAME | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | Email::Outlook::Message::Base - Base parser for .msg files. | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | This is an internal module of Email::Outlook::Message. | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | =head1 METHODS | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | =over 8 | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | =item B | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | =item B | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | =item B | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | =item B | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | =item B | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | =back | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | =head1 AUTHOR | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | Matijs van Zuijlen, C | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | Copyright 2002--2020 by Matijs van Zuijlen | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | This module is free software; you can redistribute it and/or modify | 
| 35 |  |  |  |  |  |  | it under the same terms as Perl itself. | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | =cut | 
| 38 | 9 |  |  | 9 |  | 68 | use strict; | 
|  | 9 |  |  |  |  | 16 |  | 
|  | 9 |  |  |  |  | 259 |  | 
| 39 | 9 |  |  | 9 |  | 45 | use warnings; | 
|  | 9 |  |  |  |  | 19 |  | 
|  | 9 |  |  |  |  | 187 |  | 
| 40 | 9 |  |  | 9 |  | 41 | use Encode; | 
|  | 9 |  |  |  |  | 18 |  | 
|  | 9 |  |  |  |  | 603 |  | 
| 41 | 9 |  |  | 9 |  | 4101 | use IO::String; | 
|  | 9 |  |  |  |  | 31997 |  | 
|  | 9 |  |  |  |  | 311 |  | 
| 42 | 9 |  |  | 9 |  | 4883 | use POSIX; | 
|  | 9 |  |  |  |  | 53899 |  | 
|  | 9 |  |  |  |  | 84 |  | 
| 43 | 9 |  |  | 9 |  | 23807 | use Carp; | 
|  | 9 |  |  |  |  | 30 |  | 
|  | 9 |  |  |  |  | 483 |  | 
| 44 | 9 |  |  | 9 |  | 6118 | use OLE::Storage_Lite; | 
|  | 9 |  |  |  |  | 168090 |  | 
|  | 9 |  |  |  |  | 482 |  | 
| 45 | 9 |  |  | 9 |  | 97 | use vars qw($VERSION); | 
|  | 9 |  |  |  |  | 24 |  | 
|  | 9 |  |  |  |  | 18626 |  | 
| 46 |  |  |  |  |  |  | $VERSION = "0.921"; | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | my $DIR_TYPE = 1; | 
| 49 |  |  |  |  |  |  | my $FILE_TYPE = 2; | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | # Variable encodings | 
| 52 |  |  |  |  |  |  | my $ENCODING_UNICODE = '001F'; | 
| 53 |  |  |  |  |  |  | my $ENCODING_ASCII = '001E'; | 
| 54 |  |  |  |  |  |  | my $ENCODING_BINARY = '0102'; | 
| 55 |  |  |  |  |  |  | my $ENCODING_DIRECTORY = '000D'; | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | our $VARIABLE_ENCODINGS = { | 
| 58 |  |  |  |  |  |  | '000D' => 'Directory', | 
| 59 |  |  |  |  |  |  | '001F' => 'Unicode', | 
| 60 |  |  |  |  |  |  | '001E' => 'Ascii?', | 
| 61 |  |  |  |  |  |  | '0102' => 'Binary', | 
| 62 |  |  |  |  |  |  | }; | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | # Fixed encodings | 
| 65 |  |  |  |  |  |  | my $ENCODING_INTEGER16 = '0002'; | 
| 66 |  |  |  |  |  |  | my $ENCODING_INTEGER32 = '0003'; | 
| 67 |  |  |  |  |  |  | my $ENCODING_BOOLEAN = '000B'; | 
| 68 |  |  |  |  |  |  | my $ENCODING_DATE = '0040'; | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | # | 
| 71 |  |  |  |  |  |  | # Descriptions partially based on mapitags.h | 
| 72 |  |  |  |  |  |  | # | 
| 73 |  |  |  |  |  |  | our $skipproperties = { | 
| 74 |  |  |  |  |  |  | # Envelope properties | 
| 75 |  |  |  |  |  |  | '0002' => "Alternate Recipient Allowed", | 
| 76 |  |  |  |  |  |  | '000B' => "Conversation Key", | 
| 77 |  |  |  |  |  |  | '0017' => "Importance", #TODO: Use this. | 
| 78 |  |  |  |  |  |  | '001A' => "Message Class", | 
| 79 |  |  |  |  |  |  | '0023' => "Originator Delivery Report Requested", | 
| 80 |  |  |  |  |  |  | '0026' => "Priority", #TODO: Use this. | 
| 81 |  |  |  |  |  |  | '0029' => "Read Receipt Requested", #TODO: Use this. | 
| 82 |  |  |  |  |  |  | '0036' => "Sensitivity", # As assessed by the Sender | 
| 83 |  |  |  |  |  |  | '003B' => "Sent Representing Search Key", | 
| 84 |  |  |  |  |  |  | '003D' => "Subject Prefix", | 
| 85 |  |  |  |  |  |  | '003F' => "Received By EntryId", | 
| 86 |  |  |  |  |  |  | '0040' => "Received By Name", | 
| 87 |  |  |  |  |  |  | # TODO: These two fields are part of the Sender field. | 
| 88 |  |  |  |  |  |  | '0041' => "Sent Representing EntryId", | 
| 89 |  |  |  |  |  |  | '0042' => "Sent Representing Name", | 
| 90 |  |  |  |  |  |  | '0043' => "Received Representing EntryId", | 
| 91 |  |  |  |  |  |  | '0044' => "Received Representing Name", | 
| 92 |  |  |  |  |  |  | '0046' => "Read Receipt EntryId", | 
| 93 |  |  |  |  |  |  | '0051' => "Received By Search Key", | 
| 94 |  |  |  |  |  |  | '0052' => "Received Representing Search Key", | 
| 95 |  |  |  |  |  |  | '0053' => "Read Receipt Search Key", | 
| 96 |  |  |  |  |  |  | # TODO: These two fields are part of the Sender field. | 
| 97 |  |  |  |  |  |  | '0064' => "Sent Representing Address Type", | 
| 98 |  |  |  |  |  |  | '0065' => "Sent Representing Email Address", | 
| 99 |  |  |  |  |  |  | '0070' => "Conversation Topic", | 
| 100 |  |  |  |  |  |  | '0071' => "Conversation Index", | 
| 101 |  |  |  |  |  |  | '0075' => "Received By Address Type", | 
| 102 |  |  |  |  |  |  | '0076' => "Received By Email Address", | 
| 103 |  |  |  |  |  |  | '0077' => "Received Representing Address Type", | 
| 104 |  |  |  |  |  |  | '0078' => "Received Representing Email Address", | 
| 105 |  |  |  |  |  |  | '007F' => "TNEF Correlation Key", | 
| 106 |  |  |  |  |  |  | # Recipient properties | 
| 107 |  |  |  |  |  |  | '0C15' => "Recipient Type", | 
| 108 |  |  |  |  |  |  | # Sender properties | 
| 109 |  |  |  |  |  |  | '0C19' => "Sender Entry Id", | 
| 110 |  |  |  |  |  |  | '0C1D' => "Sender Search Key", | 
| 111 |  |  |  |  |  |  | '0C1E' => "Sender Address Type", | 
| 112 |  |  |  |  |  |  | # Non-transmittable properties | 
| 113 |  |  |  |  |  |  | '0E02' => "Display Bcc", | 
| 114 |  |  |  |  |  |  | '0E06' => "Message Delivery Time", | 
| 115 |  |  |  |  |  |  | '0E07' => "Message Flags", | 
| 116 |  |  |  |  |  |  | '0E0A' => "Sent Mail EntryId", | 
| 117 |  |  |  |  |  |  | '0E0F' => "Responsibility", | 
| 118 |  |  |  |  |  |  | '0E1B' => "Has Attachments", | 
| 119 |  |  |  |  |  |  | '0E1D' => "Normalized Subject", | 
| 120 |  |  |  |  |  |  | '0E1F' => "RTF In Sync", | 
| 121 |  |  |  |  |  |  | '0E20' => "Attachment Size", | 
| 122 |  |  |  |  |  |  | '0E21' => "Attachment Number", | 
| 123 |  |  |  |  |  |  | '0E23' => "Internet Article Number", | 
| 124 |  |  |  |  |  |  | '0E27' => "Security Descriptor", | 
| 125 |  |  |  |  |  |  | '0E79' => "Trust Sender", | 
| 126 |  |  |  |  |  |  | '0FF4' => "Access", | 
| 127 |  |  |  |  |  |  | '0FF6' => "Instance Key", | 
| 128 |  |  |  |  |  |  | '0FF7' => "Access Level", | 
| 129 |  |  |  |  |  |  | '0FF9' => "Record Key", | 
| 130 |  |  |  |  |  |  | '0FFE' => "Object Type", | 
| 131 |  |  |  |  |  |  | '0FFF' => "EntryId", | 
| 132 |  |  |  |  |  |  | # Content properties | 
| 133 |  |  |  |  |  |  | '1006' => "RTF Sync Body CRC", | 
| 134 |  |  |  |  |  |  | '1007' => "RTF Sync Body Count", | 
| 135 |  |  |  |  |  |  | '1008' => "RTF Sync Body Tag", | 
| 136 |  |  |  |  |  |  | '1010' => "RTF Sync Prefix Count", | 
| 137 |  |  |  |  |  |  | '1011' => "RTF Sync Trailing Count", | 
| 138 |  |  |  |  |  |  | '1046' => "Original Message ID", | 
| 139 |  |  |  |  |  |  | '1080' => "Icon Index", | 
| 140 |  |  |  |  |  |  | '1081' => "Last Verb Executed", | 
| 141 |  |  |  |  |  |  | '1082' => "Last Verb Execution Time", | 
| 142 |  |  |  |  |  |  | '10F3' => "URL Component Name", | 
| 143 |  |  |  |  |  |  | '10F4' => "Attribute Hidden", | 
| 144 |  |  |  |  |  |  | '10F5' => "Attribute System", | 
| 145 |  |  |  |  |  |  | '10F6' => "Attribute Read Only", | 
| 146 |  |  |  |  |  |  | # 'Common property' | 
| 147 |  |  |  |  |  |  | '3000' => "Row Id", | 
| 148 |  |  |  |  |  |  | '3001' => "Display Name", | 
| 149 |  |  |  |  |  |  | '3002' => "Address Type", | 
| 150 |  |  |  |  |  |  | '3007' => "Creation Time", | 
| 151 |  |  |  |  |  |  | '3008' => "Last Modification Time", | 
| 152 |  |  |  |  |  |  | '300B' => "Search Key", | 
| 153 |  |  |  |  |  |  | # Message store info | 
| 154 |  |  |  |  |  |  | '340D' => "Store Support Mask", | 
| 155 |  |  |  |  |  |  | '3414' => "Message Store Provider", | 
| 156 |  |  |  |  |  |  | # Attachment properties | 
| 157 |  |  |  |  |  |  | '3702' => "Attachment Encoding", | 
| 158 |  |  |  |  |  |  | '3703' => "Attachment Extension", | 
| 159 |  |  |  |  |  |  | # TODO: Use the following to distinguish between nested msg and other OLE | 
| 160 |  |  |  |  |  |  | # stores. | 
| 161 |  |  |  |  |  |  | '3705' => "Attachment Method", | 
| 162 |  |  |  |  |  |  | '3709' => "Attachment Rendering", # Icon as WMF | 
| 163 |  |  |  |  |  |  | '370A' => "Tag identifying application that supplied the attachment", | 
| 164 |  |  |  |  |  |  | '370B' => "Attachment Rendering Position", | 
| 165 |  |  |  |  |  |  | '3713' => "Attachment Content Location", #TODO: Use this? | 
| 166 |  |  |  |  |  |  | # 3900 -- 39FF: 'Address book' | 
| 167 |  |  |  |  |  |  | '3900' => "Address Book Display Type", | 
| 168 |  |  |  |  |  |  | '39FF' => "Address Book 7 Bit Display Name", | 
| 169 |  |  |  |  |  |  | # Mail User Object | 
| 170 |  |  |  |  |  |  | '3A00' => "Account", | 
| 171 |  |  |  |  |  |  | '3A20' => "Transmittable Display Name", | 
| 172 |  |  |  |  |  |  | '3A40' => "Send Rich Info", | 
| 173 |  |  |  |  |  |  | # 'Display table properties' | 
| 174 |  |  |  |  |  |  | '3FF8' => "Creator Name", | 
| 175 |  |  |  |  |  |  | '3FF9' => "Creator EntryId", | 
| 176 |  |  |  |  |  |  | '3FFA' => "Last Modifier Name", | 
| 177 |  |  |  |  |  |  | '3FFB' => "Last Modifier EntryId", | 
| 178 |  |  |  |  |  |  | '3FFD' => "Message Code Page", | 
| 179 |  |  |  |  |  |  | # 'Transport-defined envelope property' | 
| 180 |  |  |  |  |  |  | '4019' => "Sender Flags", | 
| 181 |  |  |  |  |  |  | '401A' => "Sent Representing Flags", | 
| 182 |  |  |  |  |  |  | '401B' => "Received By Flags", | 
| 183 |  |  |  |  |  |  | '401C' => "Received Representing Flags", | 
| 184 |  |  |  |  |  |  | '4029' => "Read Receipt Address Type", | 
| 185 |  |  |  |  |  |  | '402A' => "Read Receipt Email Address", | 
| 186 |  |  |  |  |  |  | '402B' => "Read Receipt Name", | 
| 187 |  |  |  |  |  |  | '5FF6' => "Recipient Display Name", | 
| 188 |  |  |  |  |  |  | '5FF7' => "Recipient EntryId", | 
| 189 |  |  |  |  |  |  | '5FFD' => "Recipient Flags", | 
| 190 |  |  |  |  |  |  | '5FFF' => "Recipient Track Status", | 
| 191 |  |  |  |  |  |  | # 'Provider-defined internal non-transmittable property' | 
| 192 |  |  |  |  |  |  | '664A' => "Has Named Properties", | 
| 193 |  |  |  |  |  |  | '6740' => "Sent Mail Server EntryId", | 
| 194 |  |  |  |  |  |  | }; | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | sub new { | 
| 197 | 16 |  |  | 16 | 1 | 51 | my ($class, $pps, $verbose) = @_; | 
| 198 | 16 |  |  |  |  | 75 | my $self = bless { | 
| 199 |  |  |  |  |  |  | _pps_file_entries => {}, | 
| 200 |  |  |  |  |  |  | _pps => $pps | 
| 201 |  |  |  |  |  |  | }, $class; | 
| 202 | 16 |  |  |  |  | 90 | $self->_set_verbosity($verbose); | 
| 203 | 16 |  |  |  |  | 93 | $self->_process_pps($pps); | 
| 204 | 16 |  |  |  |  | 51 | return $self; | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | sub mapi_property_names { | 
| 208 | 28 |  |  | 28 | 1 | 54 | my $self = shift; | 
| 209 | 28 |  |  |  |  | 50 | return keys %{$self->{_pps_file_entries}}; | 
|  | 28 |  |  |  |  | 227 |  | 
| 210 |  |  |  |  |  |  | } | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | sub get_mapi_property { | 
| 213 | 215 |  |  | 215 | 1 | 856 | my ($self, $code) = @_; | 
| 214 | 215 |  |  |  |  | 504 | return $self->{_pps_file_entries}->{$code}; | 
| 215 |  |  |  |  |  |  | } | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | sub set_mapi_property { | 
| 218 | 680 |  |  | 680 | 1 | 1081 | my ($self, $code, $data) = @_; | 
| 219 | 680 |  |  |  |  | 1466 | $self->{_pps_file_entries}->{$code} = $data; | 
| 220 | 680 |  |  |  |  | 976 | return; | 
| 221 |  |  |  |  |  |  | } | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | sub property { | 
| 224 | 52 |  |  | 52 | 1 | 118 | my ($self, $name) = @_; | 
| 225 | 52 |  |  |  |  | 110 | my $map = $self->_property_map; | 
| 226 |  |  |  |  |  |  | # TODO: Prepare reverse map instead of doing dumb lookup. | 
| 227 | 52 |  |  |  |  | 83 | foreach my $code (keys %{$map}) { | 
|  | 52 |  |  |  |  | 150 |  | 
| 228 | 209 |  |  |  |  | 296 | my $key = $map->{$code}; | 
| 229 | 209 | 100 |  |  |  | 371 | next unless $key eq $name; | 
| 230 | 52 |  |  |  |  | 114 | my $prop = $self->get_mapi_property($code); | 
| 231 | 52 | 100 |  |  |  | 130 | if ($prop) { | 
| 232 | 42 |  |  |  |  | 58 | my ($encoding, $data) = @{$prop}; | 
|  | 42 |  |  |  |  | 92 |  | 
| 233 | 42 |  |  |  |  | 112 | return $self->_decode_mapi_property($encoding, $data); | 
| 234 |  |  |  |  |  |  | } else { | 
| 235 | 10 |  |  |  |  | 60 | return; | 
| 236 |  |  |  |  |  |  | } | 
| 237 |  |  |  |  |  |  | } | 
| 238 | 0 |  |  |  |  | 0 | return; | 
| 239 |  |  |  |  |  |  | } | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | sub _decode_mapi_property { | 
| 242 | 204 |  |  | 204 |  | 331 | my ($self, $encoding, $data) = @_; | 
| 243 |  |  |  |  |  |  |  | 
| 244 | 204 | 100 | 100 |  |  | 540 | if ($encoding eq $ENCODING_ASCII or $encoding eq $ENCODING_UNICODE) { | 
| 245 | 164 | 100 |  |  |  | 334 | if ($encoding eq $ENCODING_UNICODE) { | 
| 246 | 44 |  |  |  |  | 111 | $data = decode("UTF-16LE", $data); | 
| 247 |  |  |  |  |  |  | } | 
| 248 | 164 |  |  |  |  | 14097 | $data =~ s/ \000 $ //sgx; | 
| 249 | 164 |  |  |  |  | 629 | return $data; | 
| 250 |  |  |  |  |  |  | } | 
| 251 |  |  |  |  |  |  |  | 
| 252 | 40 | 100 |  |  |  | 131 | if ($encoding eq $ENCODING_BINARY) { | 
| 253 | 14 |  |  |  |  | 55 | return $data; | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  |  | 
| 256 | 26 | 100 |  |  |  | 79 | if ($encoding eq $ENCODING_DATE) { | 
| 257 | 16 |  |  |  |  | 68 | my @a = OLE::Storage_Lite::OLEDate2Local $data; | 
| 258 | 16 |  |  |  |  | 374 | return $self->_format_date(\@a); | 
| 259 |  |  |  |  |  |  | } | 
| 260 |  |  |  |  |  |  |  | 
| 261 | 10 | 50 |  |  |  | 40 | if ($encoding eq $ENCODING_INTEGER16) { | 
| 262 | 0 |  |  |  |  | 0 | return unpack("v", substr($data, 0, 2)); | 
| 263 |  |  |  |  |  |  | } | 
| 264 |  |  |  |  |  |  |  | 
| 265 | 10 | 50 |  |  |  | 46 | if ($encoding eq $ENCODING_INTEGER32) { | 
| 266 | 10 |  |  |  |  | 61 | return unpack("V", substr($data, 0, 4)); | 
| 267 |  |  |  |  |  |  | } | 
| 268 |  |  |  |  |  |  |  | 
| 269 | 0 | 0 |  |  |  | 0 | if ($encoding eq $ENCODING_BOOLEAN) { | 
| 270 | 0 |  |  |  |  | 0 | return unpack("C", substr($data, 0, 1)); | 
| 271 |  |  |  |  |  |  | } | 
| 272 |  |  |  |  |  |  |  | 
| 273 | 0 |  |  |  |  | 0 | warn "Unhandled encoding $encoding\n"; | 
| 274 | 0 |  |  |  |  | 0 | return $data; | 
| 275 |  |  |  |  |  |  | } | 
| 276 |  |  |  |  |  |  |  | 
| 277 |  |  |  |  |  |  | sub _process_pps { | 
| 278 | 28 |  |  | 28 |  | 71 | my ($self, $pps) = @_; | 
| 279 | 28 |  |  |  |  | 51 | foreach my $child (@{$pps->{Child}}) { | 
|  | 28 |  |  |  |  | 86 |  | 
| 280 | 470 | 100 |  |  |  | 1029 | if ($child->{Type} == $DIR_TYPE) { | 
|  |  | 50 |  |  |  |  |  | 
| 281 | 28 |  |  |  |  | 97 | $self->_process_subdirectory($child); | 
| 282 |  |  |  |  |  |  | } elsif ($child->{Type} == $FILE_TYPE) { | 
| 283 | 442 |  |  |  |  | 1162 | $self->_process_pps_file_entry($child); | 
| 284 |  |  |  |  |  |  | } else { | 
| 285 | 0 |  |  |  |  | 0 | carp "Unknown entry type: $child->{Type}"; | 
| 286 |  |  |  |  |  |  | } | 
| 287 |  |  |  |  |  |  | } | 
| 288 | 28 |  |  |  |  | 169 | $self->_check_pps_file_entries($self->_property_map); | 
| 289 | 28 |  |  |  |  | 58 | return; | 
| 290 |  |  |  |  |  |  | } | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | sub _get_pps_name { | 
| 293 | 482 |  |  | 482 |  | 664 | my ($self, $pps) = @_; | 
| 294 | 482 |  |  |  |  | 948 | my $name = OLE::Storage_Lite::Ucs2Asc($pps->{Name}); | 
| 295 | 482 |  |  |  |  | 7473 | $name =~ s/ \W / /gx; | 
| 296 | 482 |  |  |  |  | 855 | return $name; | 
| 297 |  |  |  |  |  |  | } | 
| 298 |  |  |  |  |  |  |  | 
| 299 |  |  |  |  |  |  | sub _parse_item_name { | 
| 300 | 442 |  |  | 442 |  | 757 | my ($self, $name) = @_; | 
| 301 |  |  |  |  |  |  |  | 
| 302 | 442 | 100 |  |  |  | 1139 | if ($name =~ / ^ __substg1 [ ] 0_ (....) (....) $ /x) { | 
| 303 | 414 |  |  |  |  | 964 | my ($property, $encoding) = ($1, $2); | 
| 304 | 414 |  |  |  |  | 1022 | return ($property, $encoding); | 
| 305 |  |  |  |  |  |  | } else { | 
| 306 | 28 |  |  |  |  | 61 | return (undef, undef); | 
| 307 |  |  |  |  |  |  | } | 
| 308 |  |  |  |  |  |  | } | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | sub _warn_about_unknown_directory { | 
| 311 | 12 |  |  | 12 |  | 42 | my ($self, $pps) = @_; | 
| 312 |  |  |  |  |  |  |  | 
| 313 | 12 |  |  |  |  | 35 | my $name = $self->_get_pps_name($pps); | 
| 314 | 12 | 50 |  |  |  | 49 | if ($name eq '__nameid_version1 0') { | 
| 315 |  |  |  |  |  |  | # TODO: Use this data to access so-called named properties. | 
| 316 |  |  |  |  |  |  | $self->{VERBOSE} | 
| 317 | 12 | 50 |  |  |  | 174 | and warn "Skipping DIR entry $name (Introductory stuff)\n"; | 
| 318 |  |  |  |  |  |  | } else { | 
| 319 | 0 |  |  |  |  | 0 | warn "Unknown DIR entry $name\n"; | 
| 320 |  |  |  |  |  |  | } | 
| 321 | 12 |  |  |  |  | 35 | return; | 
| 322 |  |  |  |  |  |  | } | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  | sub _warn_about_unknown_file { | 
| 325 | 0 |  |  | 0 |  | 0 | my ($self, $pps) = @_; | 
| 326 |  |  |  |  |  |  |  | 
| 327 | 0 |  |  |  |  | 0 | my $name = $self->_get_pps_name($pps); | 
| 328 |  |  |  |  |  |  |  | 
| 329 | 0 | 0 |  |  |  | 0 | if ($name eq 'Olk10SideProps_0001') { | 
| 330 |  |  |  |  |  |  | $self->{VERBOSE} | 
| 331 | 0 | 0 |  |  |  | 0 | and warn "Skipping FILE entry $name (Properties)\n"; | 
| 332 |  |  |  |  |  |  | } else { | 
| 333 | 0 |  |  |  |  | 0 | warn "Unknown FILE entry $name\n"; | 
| 334 |  |  |  |  |  |  | } | 
| 335 | 0 |  |  |  |  | 0 | return; | 
| 336 |  |  |  |  |  |  | } | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | # | 
| 339 |  |  |  |  |  |  | # Generic processor for a file entry: Inserts the entry's data into the | 
| 340 |  |  |  |  |  |  | # $self's mapi property list. | 
| 341 |  |  |  |  |  |  | # | 
| 342 |  |  |  |  |  |  | sub _process_pps_file_entry { | 
| 343 | 442 |  |  | 442 |  | 821 | my ($self, $pps) = @_; | 
| 344 | 442 |  |  |  |  | 677 | my $name = $self->_get_pps_name($pps); | 
| 345 | 442 |  |  |  |  | 822 | my ($property, $encoding) = $self->_parse_item_name($name); | 
| 346 |  |  |  |  |  |  |  | 
| 347 | 442 | 100 |  |  |  | 824 | if (defined $property) { | 
|  |  | 50 |  |  |  |  |  | 
| 348 | 414 |  |  |  |  | 1091 | $self->set_mapi_property($property, [$encoding, $pps->{Data}]); | 
| 349 |  |  |  |  |  |  | } elsif ($name eq '__properties_version1 0') { | 
| 350 | 28 |  |  |  |  | 124 | $self->_process_property_stream ($pps->{Data}); | 
| 351 |  |  |  |  |  |  | } else { | 
| 352 | 0 |  |  |  |  | 0 | $self->_warn_about_unknown_file($pps); | 
| 353 |  |  |  |  |  |  | } | 
| 354 | 442 |  |  |  |  | 754 | return; | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | sub _process_property_stream { | 
| 358 | 28 |  |  | 28 |  | 67 | my ($self, $data) = @_; | 
| 359 | 28 |  |  |  |  | 122 | my ($n, $len) = ($self->_property_stream_header_length, length $data) ; | 
| 360 |  |  |  |  |  |  |  | 
| 361 | 28 |  |  |  |  | 89 | while ($n + 16 <= $len) { | 
| 362 | 680 |  |  |  |  | 1243 | my @f = unpack "v4", substr $data, $n, 8; | 
| 363 |  |  |  |  |  |  |  | 
| 364 | 680 |  |  |  |  | 1287 | my $encoding = sprintf("%04X", $f[0]); | 
| 365 |  |  |  |  |  |  |  | 
| 366 | 680 | 100 |  |  |  | 1312 | unless ($VARIABLE_ENCODINGS->{$encoding}) { | 
| 367 | 266 |  |  |  |  | 482 | my $property = sprintf("%04X", $f[1]); | 
| 368 | 266 |  |  |  |  | 425 | my $propdata = substr $data, $n+8, 8; | 
| 369 | 266 |  |  |  |  | 659 | $self->set_mapi_property($property, [$encoding, $propdata]); | 
| 370 |  |  |  |  |  |  | } | 
| 371 |  |  |  |  |  |  | } continue { | 
| 372 | 680 |  |  |  |  | 1144 | $n += 16 ; | 
| 373 |  |  |  |  |  |  | } | 
| 374 | 28 |  |  |  |  | 58 | return; | 
| 375 |  |  |  |  |  |  | } | 
| 376 |  |  |  |  |  |  |  | 
| 377 |  |  |  |  |  |  | sub _check_pps_file_entries { | 
| 378 | 28 |  |  | 28 |  | 63 | my ($self, $map) = @_; | 
| 379 |  |  |  |  |  |  |  | 
| 380 | 28 |  |  |  |  | 135 | foreach my $property ($self->mapi_property_names) { | 
| 381 | 680 | 100 |  |  |  | 1206 | if (my $key = $map->{$property}) { | 
| 382 | 162 |  |  |  |  | 327 | $self->_use_property($key, $property); | 
| 383 |  |  |  |  |  |  | } else { | 
| 384 | 518 |  |  |  |  | 849 | $self->_warn_about_skipped_property($property); | 
| 385 |  |  |  |  |  |  | } | 
| 386 |  |  |  |  |  |  | } | 
| 387 | 28 |  |  |  |  | 86 | return; | 
| 388 |  |  |  |  |  |  | } | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | sub _use_property { | 
| 391 | 162 |  |  | 162 |  | 628 | my ($self, $key, $property) = @_; | 
| 392 | 162 |  |  |  |  | 256 | my ($encoding, $data) = @{$self->get_mapi_property($property)}; | 
|  | 162 |  |  |  |  | 313 |  | 
| 393 | 162 |  |  |  |  | 328 | $self->{$key} = $self->_decode_mapi_property($encoding, $data); | 
| 394 | 162 |  |  |  |  | 534 | $self->{"${key}_ENCODING"} = $encoding; | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | $self->{VERBOSE} | 
| 397 | 162 | 50 |  |  |  | 339 | and $self->_log_property("Using   ", $property, $key); | 
| 398 | 162 |  |  |  |  | 297 | return; | 
| 399 |  |  |  |  |  |  | } | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | sub _warn_about_skipped_property { | 
| 402 | 518 |  |  | 518 |  | 708 | my ($self, $property) = @_; | 
| 403 |  |  |  |  |  |  |  | 
| 404 | 518 | 50 |  |  |  | 1002 | return unless $self->{VERBOSE}; | 
| 405 |  |  |  |  |  |  |  | 
| 406 | 0 |  | 0 |  |  | 0 | my $meaning = $skipproperties->{$property} || "UNKNOWN"; | 
| 407 |  |  |  |  |  |  |  | 
| 408 | 0 |  |  |  |  | 0 | $self->_log_property("Skipping", $property, $meaning); | 
| 409 | 0 |  |  |  |  | 0 | return; | 
| 410 |  |  |  |  |  |  | } | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | sub _log_property { | 
| 413 | 0 |  |  | 0 |  | 0 | my ($self, $message, $property, $meaning) = @_; | 
| 414 |  |  |  |  |  |  |  | 
| 415 | 0 |  |  |  |  | 0 | my ($encoding, $data) = @{$self->get_mapi_property($property)}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 416 | 0 |  |  |  |  | 0 | my $value = $self->_decode_mapi_property($encoding, $data); | 
| 417 | 0 |  |  |  |  | 0 | $value = substr($value, 0, 50); | 
| 418 |  |  |  |  |  |  |  | 
| 419 | 0 | 0 |  |  |  | 0 | if ($encoding eq $ENCODING_BINARY) { | 
| 420 | 0 | 0 |  |  |  | 0 | if ($value =~ / [[:print:]] /x) { | 
| 421 | 0 |  |  |  |  | 0 | $value =~ s/ [^[:print:]] /./gx; | 
| 422 |  |  |  |  |  |  | } else { | 
| 423 | 0 |  |  |  |  | 0 | $value =~ s/ . / sprintf("%02x ", ord($&)) /sgex; | 
|  | 0 |  |  |  |  | 0 |  | 
| 424 |  |  |  |  |  |  | } | 
| 425 |  |  |  |  |  |  | } | 
| 426 |  |  |  |  |  |  |  | 
| 427 | 0 | 0 |  |  |  | 0 | if (length($value) > 45) { | 
| 428 | 0 |  |  |  |  | 0 | $value = substr($value, 0, 41) . " ..."; | 
| 429 |  |  |  |  |  |  | } | 
| 430 |  |  |  |  |  |  |  | 
| 431 | 0 |  |  |  |  | 0 | warn "$message property $encoding:$property ($meaning): $value\n"; | 
| 432 | 0 |  |  |  |  | 0 | return; | 
| 433 |  |  |  |  |  |  | } | 
| 434 |  |  |  |  |  |  |  | 
| 435 |  |  |  |  |  |  | sub _set_verbosity { | 
| 436 | 28 |  |  | 28 |  | 67 | my ($self, $verbosity) = @_; | 
| 437 | 28 | 50 |  |  |  | 138 | $self->{VERBOSE} = $verbosity ? 1 : 0; | 
| 438 | 28 |  |  |  |  | 54 | return; | 
| 439 |  |  |  |  |  |  | } | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | # | 
| 442 |  |  |  |  |  |  | # Format a gmt date according to RFC822 | 
| 443 |  |  |  |  |  |  | # | 
| 444 |  |  |  |  |  |  | sub _format_date { | 
| 445 | 31 |  |  | 31 |  | 70 | my ($self, $datearr) = @_; | 
| 446 | 31 |  |  |  |  | 55 | my $day = qw(Sun Mon Tue Wed Thu Fri Sat)[strftime("%w", @{$datearr})]; | 
|  | 31 |  |  |  |  | 1525 |  | 
| 447 | 31 |  |  |  |  | 115 | my $month = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)[strftime("%m", @{$datearr}) - 1]; | 
|  | 31 |  |  |  |  | 756 |  | 
| 448 | 31 |  |  |  |  | 127 | return strftime("$day, %d $month %Y %H:%M:%S +0000", @{$datearr}); | 
|  | 31 |  |  |  |  | 834 |  | 
| 449 |  |  |  |  |  |  | } | 
| 450 |  |  |  |  |  |  |  | 
| 451 |  |  |  |  |  |  | 1; |