| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Mail::Exchange::Message; | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | =head1 NAME | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | Mail::Exchange::Message - class to deal with .msg files, used by Microsoft | 
| 6 |  |  |  |  |  |  | Exchange / MS Outlook. | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | use Mail::Exchange::Message; | 
| 11 |  |  |  |  |  |  | use Mail::Exchange::Message::MessageFlags; | 
| 12 |  |  |  |  |  |  | use Mail::Exchange::Recipient; | 
| 13 |  |  |  |  |  |  | use Mail::Exchange::Attachment; | 
| 14 |  |  |  |  |  |  | use Mail::Exchange::PidTagIDs; | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | # modify an existing .msg file | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | my $msg=Mail::Exchange::Message->new("my.msg"); | 
| 19 |  |  |  |  |  |  | print "old Subject: ", $msg->get(PidTagSubject), "\n"; | 
| 20 |  |  |  |  |  |  | $msg->setSubject('new subject'); | 
| 21 |  |  |  |  |  |  | $msg->save("changed.msg"); | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | # create a .msg file from scratch, and send it to | 
| 24 |  |  |  |  |  |  | # the browser from a CGI script | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | my $msg=Mail::Exchange::Message->new(); | 
| 27 |  |  |  |  |  |  | $msg->setUnicode(1); | 
| 28 |  |  |  |  |  |  | $msg->setSubject('message subject'); | 
| 29 |  |  |  |  |  |  | $msg->setBody('message body'); | 
| 30 |  |  |  |  |  |  | $msg->set(PidTagMessageFlags, mfUnsent); | 
| 31 |  |  |  |  |  |  | $message->setDisplayTo('test@somewhere.com'); | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | my $recipient=Mail::Exchange::Recipient->new(); | 
| 34 |  |  |  |  |  |  | $recipient->setEmailAddress('test@somewhere.com'); | 
| 35 |  |  |  |  |  |  | $recipient->setDisplayName('John Tester'); | 
| 36 |  |  |  |  |  |  | $recipient->setRecipientType('To'); | 
| 37 |  |  |  |  |  |  | $message->addRecipient($recipient); | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | my $attachment=Mail::Exchange::Message->new("attach.dat"); | 
| 40 |  |  |  |  |  |  | $message->addAttachment($attachment); | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | binmode(STDOUT); | 
| 43 |  |  |  |  |  |  | print STDOUT qq(Content-type: application/vnd.ms-outlook | 
| 44 |  |  |  |  |  |  | Content-Disposition: attachment; filename="newmessage.msg" | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | ); | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | $message->save(\*STDOUT); | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | Mail::Exchange::Message allows you to read and write binary message files that | 
| 53 |  |  |  |  |  |  | Microsoft Outlook uses to store emails, to-dos, appointments and so on. It does | 
| 54 |  |  |  |  |  |  | not need Windows, or Outlook, installed, and should be able to run on any | 
| 55 |  |  |  |  |  |  | operating system that supports perl. | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | It might have been named "Outlook" instead of "Exchange", but the | 
| 58 |  |  |  |  |  |  | "Mail::Outlook" and "Email::Outlook" namespaces had both been taken at the | 
| 59 |  |  |  |  |  |  | time of its implementation, and it contains some sub-modules that might be | 
| 60 |  |  |  |  |  |  | helpful to implementations of more functionality with Microsoft Exchange, | 
| 61 |  |  |  |  |  |  | with which it is intended to coexist. | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | =cut | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  |  | 
| 66 | 5 |  |  | 5 |  | 224816 | use strict; | 
|  | 5 |  |  |  |  | 14 |  | 
|  | 5 |  |  |  |  | 206 |  | 
| 67 | 5 |  |  | 5 |  | 28 | use warnings; | 
|  | 5 |  |  |  |  | 10 |  | 
|  | 5 |  |  |  |  | 201 |  | 
| 68 | 5 |  |  | 5 |  | 141 | use 5.008; | 
|  | 5 |  |  |  |  | 23 |  | 
|  | 5 |  |  |  |  | 1017 |  | 
| 69 |  |  |  |  |  |  |  | 
| 70 | 5 |  |  | 5 |  | 30 | use Exporter; | 
|  | 5 |  |  |  |  | 9 |  | 
|  | 5 |  |  |  |  | 293 |  | 
| 71 | 5 |  |  | 5 |  | 13617 | use Encode; | 
|  | 5 |  |  |  |  | 79369 |  | 
|  | 5 |  |  |  |  | 602 |  | 
| 72 | 5 |  |  | 5 |  | 3538 | use Mail::Exchange::Time; | 
|  | 5 |  |  |  |  | 13 |  | 
|  | 5 |  |  |  |  | 229 |  | 
| 73 | 5 |  |  | 5 |  | 4631 | use Mail::Exchange::PidTagIDs; | 
|  | 5 |  |  |  |  | 18 |  | 
|  | 5 |  |  |  |  | 14000 |  | 
| 74 | 5 |  |  | 5 |  | 5542 | use Mail::Exchange::PidTagDefs; | 
|  | 5 |  |  |  |  | 20 |  | 
|  | 5 |  |  |  |  | 1051 |  | 
| 75 | 5 |  |  | 5 |  | 3529 | use Mail::Exchange::PropertyContainer; | 
|  | 5 |  |  |  |  | 110 |  | 
|  | 5 |  |  |  |  | 282 |  | 
| 76 | 5 |  |  | 5 |  | 3450 | use Mail::Exchange::NamedProperties; | 
|  | 5 |  |  |  |  | 21 |  | 
|  | 5 |  |  |  |  | 363 |  | 
| 77 | 5 |  |  | 5 |  | 3296 | use Mail::Exchange::Recipient; | 
|  | 5 |  |  |  |  | 15 |  | 
|  | 5 |  |  |  |  | 295 |  | 
| 78 | 5 |  |  | 5 |  | 3076 | use Mail::Exchange::Attachment; | 
|  | 5 |  |  |  |  | 15 |  | 
|  | 5 |  |  |  |  | 247 |  | 
| 79 | 5 |  |  | 5 |  | 3347 | use Mail::Exchange::Message::MessageFlags; | 
|  | 5 |  |  |  |  | 19 |  | 
|  | 5 |  |  |  |  | 505 |  | 
| 80 | 5 |  |  | 5 |  | 34 | use OLE::Storage_Lite; | 
|  | 5 |  |  |  |  | 11 |  | 
|  | 5 |  |  |  |  | 191 |  | 
| 81 |  |  |  |  |  |  |  | 
| 82 | 5 |  |  | 5 |  | 33 | use vars qw($VERSION @ISA); | 
|  | 5 |  |  |  |  | 7 |  | 
|  | 5 |  |  |  |  | 13921 |  | 
| 83 |  |  |  |  |  |  | @ISA=qw(Mail::Exchange::PropertyContainer Exporter); | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | $VERSION = "0.04"; | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | =head2 new() | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | $msg=Mail::Exchange::Message->new([$file]) | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | Read a message from the .msg file C<$file>, or create a new, empty one, | 
| 92 |  |  |  |  |  |  | if C<$file> isn't given. | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  | =cut | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | sub new { | 
| 97 | 1 |  |  | 1 | 1 | 728 | my $class=shift; | 
| 98 | 1 |  |  |  |  | 2 | my $file=shift; | 
| 99 |  |  |  |  |  |  |  | 
| 100 | 1 |  |  |  |  | 13 | my $self=Mail::Exchange::PropertyContainer->new(); | 
| 101 | 1 |  |  |  |  | 3 | bless($self, $class); | 
| 102 |  |  |  |  |  |  |  | 
| 103 | 1 |  |  |  |  | 10 | $self->{_recipients}=(); | 
| 104 | 1 |  |  |  |  | 3 | $self->{_attachments}=(); | 
| 105 | 1 |  |  |  |  | 10 | $self->{_namedProperties}=Mail::Exchange::NamedProperties->new(); | 
| 106 |  |  |  |  |  |  |  | 
| 107 | 1 | 50 |  |  |  | 4 | if ($file) { | 
| 108 | 1 |  |  |  |  | 5 | $self->parse($file); | 
| 109 |  |  |  |  |  |  | } else { | 
| 110 |  |  |  |  |  |  | # these are taken from [MS-OXCMSG] 3.2.5.2 | 
| 111 |  |  |  |  |  |  | # PidTagMessageClass is NOT initialized, there are | 
| 112 |  |  |  |  |  |  | # subclasses for that. | 
| 113 | 0 |  |  |  |  | 0 | my $now=Mail::Exchange::Time->new(time()); | 
| 114 | 0 |  |  |  |  | 0 | $self->set(PidTagImportance,		1); | 
| 115 | 0 |  |  |  |  | 0 | $self->set(PidTagSensitivity,		0); | 
| 116 | 0 |  |  |  |  | 0 | $self->set(PidTagDisplayBcc,		""); | 
| 117 | 0 |  |  |  |  | 0 | $self->set(PidTagDisplayCc,		""); | 
| 118 | 0 |  |  |  |  | 0 | $self->set(PidTagDisplayTo,		""); | 
| 119 | 0 |  |  |  |  | 0 | $self->set(PidTagMessageFlags,		9); | 
| 120 | 0 |  |  |  |  | 0 | $self->set(PidTagMessageSize,		1); | 
| 121 | 0 |  |  |  |  | 0 | $self->set(PidTagHasAttachments,	0); | 
| 122 | 0 |  |  |  |  | 0 | $self->set(PidTagTrustSender,		1); | 
| 123 | 0 |  |  |  |  | 0 | $self->set(PidTagAccess,		3); | 
| 124 | 0 |  |  |  |  | 0 | $self->set(PidTagAccessLevel,		3); | 
| 125 | 0 |  |  |  |  | 0 | $self->set(PidTagUrlCompName,		"No Subject.EML"); | 
| 126 | 0 |  |  |  |  | 0 | $self->set(PidTagCreationTime,		$now->mstime()); | 
| 127 | 0 |  |  |  |  | 0 | $self->set(PidTagLastModificationTime,	$now->mstime()); | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  |  | 
| 130 | 1 |  |  |  |  | 217 | $self; | 
| 131 |  |  |  |  |  |  | } | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | =head2 parse() | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | $msg->parse($file) | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | Read a message file into an internal structure. Called from new() if a | 
| 138 |  |  |  |  |  |  | filename argument is given. C<$file> is expected to be a string, but may | 
| 139 |  |  |  |  |  |  | be anything that is accepted by OLE::Storage_Lite. | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | =cut | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | sub parse { | 
| 144 | 1 |  |  | 1 | 1 | 2 | my $self=shift; | 
| 145 | 1 |  |  |  |  | 3 | my $file=shift; | 
| 146 | 1 |  |  |  |  | 9 | my $OLEFile = OLE::Storage_Lite->new($file); | 
| 147 | 1 |  |  |  |  | 12 | my $root=$OLEFile->getPpsTree(1); | 
| 148 | 1 | 50 |  |  |  | 27340 | die "$file does not seem to be an OLE File" unless $root; | 
| 149 |  |  |  |  |  |  |  | 
| 150 | 1 |  |  |  |  | 9 | my $nameid=Encode::encode("UCS2LE", "__nameid_version1.0"); | 
| 151 | 1 |  |  |  |  | 5422 | foreach my $entry (@{$root->{Child}}) { | 
|  | 1 |  |  |  |  | 7 |  | 
| 152 | 22 | 100 |  |  |  | 60 | if ($entry->{Name} eq $nameid) { | 
| 153 | 1 |  |  |  |  | 7 | $self->_parsePropertyNames($entry); | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  | } | 
| 156 |  |  |  |  |  |  |  | 
| 157 | 1 |  |  |  |  | 6 | my $propid=Encode::encode("UCS2LE", "__properties_version1.0"); | 
| 158 | 1 |  |  |  |  | 54 | foreach my $entry (@{$root->{Child}}) { | 
|  | 1 |  |  |  |  | 4 |  | 
| 159 | 22 | 100 |  |  |  | 46 | if ($entry->{Name} eq $propid) { | 
| 160 | 1 |  |  |  |  | 5 | $self->_parseMessageProperties($entry, $root); | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  | } | 
| 163 |  |  |  |  |  |  |  | 
| 164 | 1 |  |  |  |  | 3 | foreach my $entry (@{$root->{Child}}) { | 
|  | 1 |  |  |  |  | 5 |  | 
| 165 |  |  |  |  |  |  | # print Encode::decode("UCS2LE", $entry->{Name}), "\n"; | 
| 166 | 22 | 100 |  |  |  | 605 | if (Encode::decode("UCS2LE", $entry->{Name}) | 
| 167 |  |  |  |  |  |  | =~ /__recip_version1.0_#([0-9A-F]{8})/) { | 
| 168 | 1 |  |  |  |  | 42 | my $idx=hex($1); | 
| 169 | 1 |  |  |  |  | 3 | foreach my $subentry (@{$entry->{Child}}) { | 
|  | 1 |  |  |  |  | 4 |  | 
| 170 | 10 | 100 |  |  |  | 26 | if ($subentry->{Name} eq $propid) { | 
| 171 | 1 |  |  |  |  | 15 | $self->{_recipients}[$idx]=Mail::Exchange::Recipient->new(); | 
| 172 | 1 |  |  |  |  | 8 | $self->{_recipients}[$idx]->_parseRecipientProperties( | 
| 173 |  |  |  |  |  |  | $subentry, $entry, $self->{_namedProperties}); | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  | } | 
| 177 |  |  |  |  |  |  |  | 
| 178 | 22 | 50 |  |  |  | 600 | if (Encode::decode("UCS2LE", $entry->{Name}) | 
| 179 |  |  |  |  |  |  | =~ /__attach_version1.0_#([0-9A-F]{8})/) { | 
| 180 | 0 |  |  |  |  | 0 | my $idx=hex($1); | 
| 181 | 0 |  |  |  |  | 0 | foreach my $subentry (@{$entry->{Child}}) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 182 | 0 | 0 |  |  |  | 0 | if ($subentry->{Name} eq $propid) { | 
| 183 | 0 |  |  |  |  | 0 | $self->{_attachments}[$idx]=Mail::Exchange::Attachment->new(); | 
| 184 | 0 |  |  |  |  | 0 | $self->{_attachments}[$idx]->_parseAttachmentProperties( | 
| 185 |  |  |  |  |  |  | $subentry, $entry, $self->{_namedProperties}); | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  | } | 
| 188 |  |  |  |  |  |  | } | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  | } | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | sub _parsePropertyNames { | 
| 193 | 1 |  |  | 1 |  | 3 | my $self=shift; | 
| 194 | 1 |  |  |  |  | 3 | my $dir=shift; | 
| 195 |  |  |  |  |  |  |  | 
| 196 | 1 |  |  |  |  | 2 | my $stringstreamdata; | 
| 197 |  |  |  |  |  |  | my $guidstreamdata; | 
| 198 | 1 |  |  |  |  | 6 | my $ssid=Encode::encode("UCS2LE", "__substg1.0_00040102"); | 
| 199 | 1 |  |  |  |  | 45 | my $gsid=Encode::encode("UCS2LE", "__substg1.0_00020102"); | 
| 200 | 1 |  |  |  |  | 28 | foreach my $item (@{$dir->{Child}}) { | 
|  | 1 |  |  |  |  | 4 |  | 
| 201 | 24 | 100 |  |  |  | 69 | if ($item->{Name} eq $ssid) { | 
| 202 | 1 |  |  |  |  | 4 | $stringstreamdata=$item->{Data}; | 
| 203 |  |  |  |  |  |  | } | 
| 204 | 24 | 100 |  |  |  | 43 | if ($item->{Name} eq $gsid) { | 
| 205 | 1 |  |  |  |  | 3 | $guidstreamdata=$item->{Data}; | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 1 |  |  |  |  | 5 | my $psid=Encode::encode("UCS2LE", "__substg1.0_00030102"); | 
| 210 | 1 |  |  |  |  | 32 | foreach my $item (@{$dir->{Child}}) { | 
|  | 1 |  |  |  |  | 3 |  | 
| 211 | 24 | 100 |  |  |  | 66 | if ($item->{Name} eq $psid) { | 
| 212 | 1 |  |  |  |  | 5 | my $data=$item->{Data}; | 
| 213 | 1 |  |  |  |  | 4 | while ($data ne "") { | 
| 214 | 27 |  |  |  |  | 55 | my ($niso, $iko)=unpack("VV", $data); | 
| 215 | 27 |  |  |  |  | 36 | my $pi=($iko>>16)&0xffff; | 
| 216 | 27 |  |  |  |  | 34 | my $gi=($iko>>1)&0x7fff; | 
| 217 | 27 |  |  |  |  | 27 | my $pk=$iko&1; | 
| 218 | 27 |  |  |  |  | 27 | my $guid; | 
| 219 |  |  |  |  |  |  | my $name; | 
| 220 | 27 | 50 |  |  |  | 45 | if ($gi==1) { $guid="PS_MAPI"; } | 
|  | 0 |  |  |  |  | 0 |  | 
| 221 | 27 | 50 |  |  |  | 45 | if ($gi==2) { $guid="PS_PUBLIC_STRINGS"; } | 
|  | 0 |  |  |  |  | 0 |  | 
| 222 | 27 | 50 |  |  |  | 51 | if ($gi>2)  { $guid=GUIDDecode(substr($guidstreamdata, 16*($gi-3), 16)); } | 
|  | 27 |  |  |  |  | 92 |  | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | # We don't know the type here, so we just | 
| 225 |  |  |  |  |  |  | # add the property with undef type. The type | 
| 226 |  |  |  |  |  |  | # will be set later when we actually read | 
| 227 |  |  |  |  |  |  | # the value from the properties stream. | 
| 228 | 27 | 50 |  |  |  | 117 | if ($pk==0) { | 
| 229 | 27 |  |  |  |  | 87 | $self->{_namedProperties}->namedPropertyIndex( | 
| 230 |  |  |  |  |  |  | $niso, undef, $guid); | 
| 231 |  |  |  |  |  |  | } else { | 
| 232 | 0 |  |  |  |  | 0 | my $len=unpack("V", substr($stringstreamdata, $niso, 4)); | 
| 233 | 0 |  |  |  |  | 0 | $name=Encode::decode("UCS2LE", substr($stringstreamdata, $niso+4, $len)); | 
| 234 | 0 |  |  |  |  | 0 | $self->{_namedProperties}->namedPropertyIndex($name, undef, $guid); | 
| 235 |  |  |  |  |  |  | # @@@ die if returncode != $pi ?? | 
| 236 |  |  |  |  |  |  | } | 
| 237 | 27 |  |  |  |  | 87 | $data=substr($data, 8); | 
| 238 |  |  |  |  |  |  | } | 
| 239 |  |  |  |  |  |  | } | 
| 240 |  |  |  |  |  |  | } | 
| 241 |  |  |  |  |  |  | } | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | sub _parseMessageProperties { | 
| 244 | 1 |  |  | 1 |  | 3 | my $self=shift; | 
| 245 | 1 |  |  |  |  | 2 | my $file=shift; | 
| 246 | 1 |  |  |  |  | 4 | my $dir=shift; | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 1 |  |  |  |  | 25 | $self->_parseProperties($file, $dir, 32, $self->{_namedProperties}); | 
| 249 |  |  |  |  |  |  | } | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | =head2 set() | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | $msg->set($tag, $value, [$flags,] [$type,] [$guid]) | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | Set a property within a message. C<$tag> can be any numeric property defined in | 
| 256 |  |  |  |  |  |  | Mail::Exchange::PidTagIDs.pm, a numeric named property defined in | 
| 257 |  |  |  |  |  |  | Mail::Exchange::PidLidIDs.pm, or a string property. C<$value> is the value | 
| 258 |  |  |  |  |  |  | the property is set to. | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | C<$flags> is a bit-wise or of 1 (this property is mandatory and must not be | 
| 261 |  |  |  |  |  |  | deleted), 2 (property is writable) and 4 (property is readable and may be | 
| 262 |  |  |  |  |  |  | displayed to the user). Default is 6. | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | When a string named property is defined for the first time, its C<$type> | 
| 265 |  |  |  |  |  |  | and C<$guid> must be given as well, as stated [MS-OXPROPS], section 1.3.2 | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | =cut | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | sub set { | 
| 270 | 59 |  |  | 59 | 1 | 103 | my ($self, $tag, $value, $flags, $type, $guid) = @_; | 
| 271 | 59 |  |  |  |  | 179 | Mail::Exchange::PropertyContainer::set($self, $tag, $value, | 
| 272 |  |  |  |  |  |  | $flags, $type, $guid, $self->{_namedProperties}); | 
| 273 |  |  |  |  |  |  | } | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | sub get { | 
| 276 | 10 |  |  | 10 | 0 | 20 | my ($self, $tag) = @_; | 
| 277 | 10 |  |  |  |  | 53 | return Mail::Exchange::PropertyContainer::get($self, $tag, | 
| 278 |  |  |  |  |  |  | $self->{_namedProperties}); | 
| 279 |  |  |  |  |  |  | } | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | =head2 setSender() | 
| 282 |  |  |  |  |  |  |  | 
| 283 |  |  |  |  |  |  | $msg->setSender($address) | 
| 284 |  |  |  |  |  |  |  | 
| 285 |  |  |  |  |  |  | setSender is a shortcut for setting various properties that | 
| 286 |  |  |  |  |  |  | descripe the sender of a message. | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | =cut | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | sub setSender { | 
| 291 | 0 |  |  | 0 | 1 | 0 | my $self=shift; | 
| 292 | 0 |  |  |  |  | 0 | my $sender=shift; | 
| 293 |  |  |  |  |  |  |  | 
| 294 | 0 |  |  |  |  | 0 | $self->set(PidTagSentRepresentingAddressType, "SMTP"); | 
| 295 | 0 |  |  |  |  | 0 | $self->set(PidTagSentRepresentingName, $sender); | 
| 296 | 0 |  |  |  |  | 0 | $self->set(PidTagSentRepresentingEmailAddress, $sender); | 
| 297 |  |  |  |  |  |  |  | 
| 298 | 0 |  |  |  |  | 0 | $self->set(PidTagSenderAddressType, "SMTP"); | 
| 299 | 0 |  |  |  |  | 0 | $self->set(PidTagSenderName, $sender); | 
| 300 | 0 |  |  |  |  | 0 | $self->set(PidTagSenderEmailAddress, $sender); | 
| 301 | 0 |  |  |  |  | 0 | $self->set(PidTagSenderSmtpAddress, $sender); | 
| 302 |  |  |  |  |  |  | # $self->set(0x5D02, $sender); | 
| 303 |  |  |  |  |  |  | } | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | =head2 setDisplayTo() | 
| 306 |  |  |  |  |  |  |  | 
| 307 |  |  |  |  |  |  | $msg->setDisplayTo($text) | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | setDisplayTo sets the recipient list that is shown by outlook in the | 
| 310 |  |  |  |  |  |  | "To:" address line. | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | =cut | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | sub setDisplayTo { | 
| 315 | 0 |  |  | 0 | 1 | 0 | my $self=shift; | 
| 316 | 0 |  |  |  |  | 0 | my $recipient=shift; | 
| 317 |  |  |  |  |  |  |  | 
| 318 | 0 |  |  |  |  | 0 | $self->set(PidTagDisplayTo, $recipient); | 
| 319 |  |  |  |  |  |  | } | 
| 320 |  |  |  |  |  |  |  | 
| 321 |  |  |  |  |  |  | =head2 setDisplayCc() | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | $msg->setDisplayCc($text) | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | setDisplayCc sets the recipient list that is shown by outlook in the | 
| 326 |  |  |  |  |  |  | "Cc:" address line. | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | =cut | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | sub setDisplayCc { | 
| 331 | 0 |  |  | 0 | 1 | 0 | my $self=shift; | 
| 332 | 0 |  |  |  |  | 0 | my $recipient=shift; | 
| 333 |  |  |  |  |  |  |  | 
| 334 | 0 |  |  |  |  | 0 | $self->set(PidTagDisplayCc, $recipient); | 
| 335 |  |  |  |  |  |  | } | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | =head2 setDisplayBcc() | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | $msg->setDisplayBcc($text) | 
| 340 |  |  |  |  |  |  |  | 
| 341 |  |  |  |  |  |  | setDisplayBcc sets the recipient list that is shown by outlook in the | 
| 342 |  |  |  |  |  |  | "Bcc:" address line. | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | =cut | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | sub setDisplayBcc { | 
| 347 | 0 |  |  | 0 | 1 | 0 | my $self=shift; | 
| 348 | 0 |  |  |  |  | 0 | my $recipient=shift; | 
| 349 |  |  |  |  |  |  |  | 
| 350 | 0 |  |  |  |  | 0 | $self->set(PidTagDisplayBcc, $recipient); | 
| 351 |  |  |  |  |  |  | } | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | =head2 setSubject() | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | $msg->setSubject($text) | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | setSubject sets the subject of the message by setting various internal | 
| 358 |  |  |  |  |  |  | properties. | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | =cut | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | sub setSubject { | 
| 363 | 0 |  |  | 0 | 1 | 0 | my $self=shift; | 
| 364 | 0 |  |  |  |  | 0 | my $subject=shift; | 
| 365 |  |  |  |  |  |  |  | 
| 366 | 0 |  |  |  |  | 0 | $self->set(PidTagSubject, $subject); | 
| 367 | 0 |  |  |  |  | 0 | $self->set(PidTagSubjectPrefix, ""); | 
| 368 | 0 |  |  |  |  | 0 | $self->set(PidTagConversationTopic, $subject); | 
| 369 | 0 |  |  |  |  | 0 | $self->set(PidTagNormalizedSubject, $subject); | 
| 370 |  |  |  |  |  |  | } | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | =head2 setBody() | 
| 373 |  |  |  |  |  |  |  | 
| 374 |  |  |  |  |  |  | $msg->setBody($text) | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | setBody sets the plain text body of the message. | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | =cut | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | sub setBody { | 
| 381 | 0 |  |  | 0 | 1 | 0 | my $self=shift; | 
| 382 | 0 |  |  |  |  | 0 | my $body=shift; | 
| 383 |  |  |  |  |  |  |  | 
| 384 | 0 |  |  |  |  | 0 | $self->set(PidTagBody, $body); | 
| 385 |  |  |  |  |  |  | } | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | =head2 setHTMLBody() | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | $msg->setHTMLBody($text) | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | setHTMLBody sets the html body of the message. | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | =cut | 
| 394 |  |  |  |  |  |  |  | 
| 395 |  |  |  |  |  |  | sub setHTMLBody { | 
| 396 | 0 |  |  | 0 | 1 | 0 | my $self=shift; | 
| 397 | 0 |  |  |  |  | 0 | my $body=shift; | 
| 398 |  |  |  |  |  |  |  | 
| 399 | 0 |  |  |  |  | 0 | $self->set(PidTagHtml, $body); | 
| 400 |  |  |  |  |  |  | } | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  | =head2 setRtfBody() | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | setRtfBody($text[, $compress]) | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | setRtfBody sets the rich text format body of the message. | 
| 407 |  |  |  |  |  |  |  | 
| 408 |  |  |  |  |  |  | The message file format allows for compressed or uncompressed | 
| 409 |  |  |  |  |  |  | rtf storage. If C<$compress> is set, C<$text> will be compressed before being | 
| 410 |  |  |  |  |  |  | stored. (As of now, C<$compress> is not implemented, and C<$text> will always | 
| 411 |  |  |  |  |  |  | be stored in uncompressed form). | 
| 412 |  |  |  |  |  |  |  | 
| 413 |  |  |  |  |  |  | =cut | 
| 414 |  |  |  |  |  |  |  | 
| 415 |  |  |  |  |  |  | sub setRtfBody { | 
| 416 | 0 |  |  | 0 | 1 | 0 | my $self=shift; | 
| 417 | 0 |  |  |  |  | 0 | my $body=shift; | 
| 418 |  |  |  |  |  |  |  | 
| 419 |  |  |  |  |  |  | # OXRTFCP says CRC MUST be 0 when uncompressed. | 
| 420 |  |  |  |  |  |  |  | 
| 421 | 0 |  |  |  |  | 0 | my $header=pack("VVVV", length($body)+12, length($body), 0x414c454D, 0); | 
| 422 | 0 |  |  |  |  | 0 | $self->set(PidTagRtfCompressed, $header.$body); | 
| 423 | 0 |  |  |  |  | 0 | $self->set(PidTagRtfInSync, 1); | 
| 424 |  |  |  |  |  |  | } | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | =head2 getRtfBody() | 
| 427 |  |  |  |  |  |  |  | 
| 428 |  |  |  |  |  |  | $msg->getRtfBody() | 
| 429 |  |  |  |  |  |  |  | 
| 430 |  |  |  |  |  |  | getRtfBody gets the RTF Body of a message, uncompressing it if neccesary. | 
| 431 |  |  |  |  |  |  |  | 
| 432 |  |  |  |  |  |  | =cut | 
| 433 |  |  |  |  |  |  |  | 
| 434 |  |  |  |  |  |  | sub getRtfBody { | 
| 435 | 1 |  |  | 1 | 1 | 3 | my $self=shift; | 
| 436 | 1 |  |  |  |  | 9 | my $rtf=$self->get(PidTagRtfCompressed); | 
| 437 | 1 |  |  |  |  | 22 | my ($compsize, $rawsize, $comptype, $crc)=unpack("VVVV", $rtf); | 
| 438 | 1 |  |  |  |  | 15 | $rtf=substr($rtf, 16); | 
| 439 |  |  |  |  |  |  |  | 
| 440 | 1 | 50 |  |  |  | 9 | if ($comptype == 0x414c454D) { | 
|  |  | 50 |  |  |  |  |  | 
| 441 | 0 |  |  |  |  | 0 | return $rtf; | 
| 442 |  |  |  |  |  |  | } elsif ($comptype != 0x75465a4c) { | 
| 443 | 0 |  |  |  |  | 0 | die(sprintf("rtf compression type %08x unknown", $comptype)); | 
| 444 |  |  |  |  |  |  | } | 
| 445 |  |  |  |  |  |  |  | 
| 446 | 1 |  |  |  |  | 4 | my $dictionary='{\rtf1\ansi\mac\deff0\deftab720{\fonttbl;}'. | 
| 447 |  |  |  |  |  |  | '{\f0\fnil \froman \fswiss \fmodern '. | 
| 448 |  |  |  |  |  |  | '\fscript \fdecor MS Sans SerifSymbolArialTimes'. | 
| 449 |  |  |  |  |  |  | ' New RomanCourier{\colortbl\red0\green0\blue0'. | 
| 450 |  |  |  |  |  |  | "\r\n".'\par \pard\plain\f0\fs20\b\i\u\tab\tx'; | 
| 451 |  |  |  |  |  |  |  | 
| 452 | 1 |  |  |  |  | 3 | my $dpos=207; | 
| 453 | 1 |  |  |  |  | 2 | my $rpos=0; | 
| 454 | 1 |  |  |  |  | 3 | my $rlen=length $rtf; | 
| 455 | 1 |  |  |  |  | 3 | my $output=''; | 
| 456 |  |  |  |  |  |  | RTFTEXT: | 
| 457 | 1 |  |  |  |  | 5 | while ($rpos<$rlen) { | 
| 458 | 372 |  |  |  |  | 1577 | my $control=unpack("C", substr($rtf, $rpos++, 1)); | 
| 459 | 372 |  | 66 |  |  | 1660 | for (my $i=0; $i<8 && $rpos<$rlen; $i++) { | 
| 460 | 2975 |  |  |  |  | 3499 | my $newbyte; | 
| 461 |  |  |  |  |  |  | my $ofs; | 
| 462 | 0 |  |  |  |  | 0 | my $len; | 
| 463 | 2975 | 100 |  |  |  | 4916 | if ($control & (1<<$i)) { | 
| 464 | 2184 |  |  |  |  | 6337 | my $ref=unpack("n", substr($rtf, $rpos)); | 
| 465 | 2184 |  |  |  |  | 2960 | $rpos+=2; | 
| 466 | 2184 |  |  |  |  | 2547 | $ofs=$ref>>4; | 
| 467 | 2184 |  |  |  |  | 4526 | $len=($ref&0x0f)+2; | 
| 468 | 2184 | 100 |  |  |  | 4860 | if ($ofs==($dpos%4096)) { | 
| 469 | 1 |  |  |  |  | 8 | last RTFTEXT; | 
| 470 |  |  |  |  |  |  | } | 
| 471 | 2183 |  |  |  |  | 4908 | for (my $j=0; $j<$len; $j++) { | 
| 472 | 23289 |  |  |  |  | 58403 | $newbyte=substr($dictionary, ($ofs++%4096), 1); | 
| 473 | 23289 |  |  |  |  | 42235 | substr($dictionary, ($dpos++%4096), 1)=$newbyte; | 
| 474 | 23289 |  |  |  |  | 70541 | $output.=$newbyte; | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | } | 
| 477 |  |  |  |  |  |  | } else { | 
| 478 | 791 |  |  |  |  | 1217 | $newbyte=substr($rtf, $rpos++, 1); | 
| 479 | 791 |  |  |  |  | 825 | $output.=$newbyte; | 
| 480 | 791 |  |  |  |  | 3502 | substr($dictionary, ($dpos++%4096), 1)=$newbyte; | 
| 481 |  |  |  |  |  |  | } | 
| 482 |  |  |  |  |  |  | } | 
| 483 |  |  |  |  |  |  | } | 
| 484 | 1 |  |  |  |  | 64 | return $output; | 
| 485 |  |  |  |  |  |  | } | 
| 486 |  |  |  |  |  |  |  | 
| 487 |  |  |  |  |  |  | =head2 setHtmlBody() | 
| 488 |  |  |  |  |  |  |  | 
| 489 |  |  |  |  |  |  | $msg->setHtmlBody($htmltext) | 
| 490 |  |  |  |  |  |  |  | 
| 491 |  |  |  |  |  |  | setHtmlBody sets the html version of the message body. It is a shortcut | 
| 492 |  |  |  |  |  |  | for C<$msg->set(PidTagHtml, $htmltext)>. | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | =cut | 
| 495 |  |  |  |  |  |  |  | 
| 496 |  |  |  |  |  |  | sub setHtmlBody { | 
| 497 | 0 |  |  | 0 | 1 |  | my $self=shift; | 
| 498 | 0 |  |  |  |  |  | my $body=shift; | 
| 499 |  |  |  |  |  |  |  | 
| 500 | 0 |  |  |  |  |  | $self->set(PidTagHtml, $body); | 
| 501 |  |  |  |  |  |  | } | 
| 502 |  |  |  |  |  |  |  | 
| 503 |  |  |  |  |  |  | =head2 setUnicode() | 
| 504 |  |  |  |  |  |  |  | 
| 505 |  |  |  |  |  |  | $msg->setUnicode($flag) | 
| 506 |  |  |  |  |  |  |  | 
| 507 |  |  |  |  |  |  | If C<$flag> is 0, all strings within the message will be stored one byte per | 
| 508 |  |  |  |  |  |  | character. If C<$flag> is 1, strings will be stored in what's called unicode | 
| 509 |  |  |  |  |  |  | in the documentation (actually, UCS2LE encoded strings, using 2 bytes per | 
| 510 |  |  |  |  |  |  | character). | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | =cut | 
| 513 |  |  |  |  |  |  |  | 
| 514 |  |  |  |  |  |  | sub setUnicode { | 
| 515 | 0 |  |  | 0 | 1 |  | my $self=shift; | 
| 516 | 0 |  |  |  |  |  | my $flag=shift; | 
| 517 | 0 |  |  |  |  |  | my $mask=$self->get(PidTagStoreSupportMask); | 
| 518 | 0 | 0 |  |  |  |  | if ($flag) { | 
| 519 | 0 |  |  |  |  |  | $mask |= 0x40000; | 
| 520 |  |  |  |  |  |  | } else { | 
| 521 | 0 |  |  |  |  |  | $mask &= ~0x40000; | 
| 522 |  |  |  |  |  |  | } | 
| 523 | 0 |  |  |  |  |  | $self->set(PidTagStoreSupportMask, $mask); | 
| 524 |  |  |  |  |  |  | } | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | =head2 save() | 
| 527 |  |  |  |  |  |  |  | 
| 528 |  |  |  |  |  |  | save($msgfile) | 
| 529 |  |  |  |  |  |  |  | 
| 530 |  |  |  |  |  |  | Saves a message object to a file. $msgfile may be a file, or anything else | 
| 531 |  |  |  |  |  |  | that OLE::Storage_Lite accepts. | 
| 532 |  |  |  |  |  |  |  | 
| 533 |  |  |  |  |  |  | =cut | 
| 534 |  |  |  |  |  |  |  | 
| 535 |  |  |  |  |  |  | sub save { | 
| 536 | 0 |  |  | 0 | 1 |  | my $self=shift; | 
| 537 | 0 |  |  |  |  |  | my $output=shift; | 
| 538 |  |  |  |  |  |  |  | 
| 539 | 0 |  |  |  |  |  | my @streams=(); | 
| 540 | 0 |  |  |  |  |  | push(@streams, $self->{_namedProperties}->OleContainer()); | 
| 541 |  |  |  |  |  |  |  | 
| 542 | 0 |  |  |  |  |  | my $unicode=$self->get(PidTagStoreSupportMask)&0x40000; | 
| 543 | 0 |  |  |  |  |  | my $header=pack("V8", | 
| 544 |  |  |  |  |  |  | 0, 0, | 
| 545 | 0 |  |  |  |  |  | $#{$self->{_recipients}}+1, | 
| 546 | 0 |  |  |  |  |  | $#{$self->{_attachments}}+1, | 
| 547 | 0 |  |  |  |  |  | $#{$self->{_recipients}}+1, | 
| 548 | 0 |  |  |  |  |  | $#{$self->{_attachments}}+1, | 
| 549 |  |  |  |  |  |  | 0, 0); | 
| 550 |  |  |  |  |  |  |  | 
| 551 |  |  |  |  |  |  |  | 
| 552 | 0 |  |  |  |  |  | push(@streams, $self->_OlePropertyStreamlist($unicode, $header)); | 
| 553 |  |  |  |  |  |  |  | 
| 554 | 0 |  |  |  |  |  | foreach my $i (0..$#{$self->{_recipients}}) { | 
|  | 0 |  |  |  |  |  |  | 
| 555 | 0 |  |  |  |  |  | push(@streams, $self->{_recipients}[$i]->OleContainer($i, $unicode)); | 
| 556 |  |  |  |  |  |  | } | 
| 557 |  |  |  |  |  |  |  | 
| 558 | 0 |  |  |  |  |  | foreach my $i (0..$#{$self->{_attachments}}) { | 
|  | 0 |  |  |  |  |  |  | 
| 559 | 0 |  |  |  |  |  | push(@streams, $self->{_attachments}[$i]->OleContainer($i, $unicode)); | 
| 560 |  |  |  |  |  |  | } | 
| 561 |  |  |  |  |  |  |  | 
| 562 | 0 |  |  |  |  |  | my @ltime=localtime(); | 
| 563 | 0 |  |  |  |  |  | my $root=OLE::Storage_Lite::PPS::Root->new(\@ltime, \@ltime, \@streams); | 
| 564 | 0 |  |  |  |  |  | $root->save($output); | 
| 565 |  |  |  |  |  |  | } | 
| 566 |  |  |  |  |  |  |  | 
| 567 |  |  |  |  |  |  | =head2 addAttachment() | 
| 568 |  |  |  |  |  |  |  | 
| 569 |  |  |  |  |  |  | addAttachment($object) | 
| 570 |  |  |  |  |  |  |  | 
| 571 |  |  |  |  |  |  | Adds a Mail::Exchange::Attachment object to a message. | 
| 572 |  |  |  |  |  |  |  | 
| 573 |  |  |  |  |  |  | =cut | 
| 574 |  |  |  |  |  |  |  | 
| 575 |  |  |  |  |  |  | sub addAttachment($$) { | 
| 576 | 0 |  |  | 0 | 1 |  | my $self=shift; | 
| 577 | 0 |  |  |  |  |  | my $attachment=shift; | 
| 578 |  |  |  |  |  |  |  | 
| 579 | 0 |  |  |  |  |  | push(@{$self->{_attachments}}, $attachment); | 
|  | 0 |  |  |  |  |  |  | 
| 580 | 0 |  |  |  |  |  | $self->set(PidTagHasAttachments, 1); | 
| 581 | 0 |  | 0 |  |  |  | my $flags=$self->get(PidTagMessageFlags) || 0; | 
| 582 | 0 |  |  |  |  |  | $self->set(PidTagMessageFlags, $flags | mfHasAttach); | 
| 583 |  |  |  |  |  |  | } | 
| 584 |  |  |  |  |  |  |  | 
| 585 |  |  |  |  |  |  | =head2 addRecipient() | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | addRecipient($object) | 
| 588 |  |  |  |  |  |  |  | 
| 589 |  |  |  |  |  |  | Adds a Mail::Exchange::Recipient object to a message. | 
| 590 |  |  |  |  |  |  |  | 
| 591 |  |  |  |  |  |  | =cut | 
| 592 |  |  |  |  |  |  |  | 
| 593 |  |  |  |  |  |  | sub addRecipient($$) { | 
| 594 | 0 |  |  | 0 | 1 |  | my $self=shift; | 
| 595 | 0 |  |  |  |  |  | my $recipient=shift; | 
| 596 |  |  |  |  |  |  |  | 
| 597 | 0 |  |  |  |  |  | push(@{$self->{_recipients}}, $recipient); | 
|  | 0 |  |  |  |  |  |  | 
| 598 |  |  |  |  |  |  | } | 
| 599 |  |  |  |  |  |  |  | 
| 600 |  |  |  |  |  |  | __END__ |