File Coverage

blib/lib/Win32/Outlook/IAF.pm
Criterion Covered Total %
statement 116 128 90.6
branch 59 80 73.7
condition 15 25 60.0
subroutine 13 14 92.8
pod 4 4 100.0
total 207 251 82.4


line stmt bran cond sub pod time code
1             package Win32::Outlook::IAF;
2            
3 2     2   45329 use warnings;
  2         5  
  2         61  
4 2     2   10 use strict;
  2         5  
  2         68  
5            
6             require Exporter;
7 2     2   11 use Carp;
  2         6  
  2         174  
8            
9 2     2   10 use vars qw($VERSION @ISA @EXPORT $AUTOLOAD);
  2         4  
  2         296  
10            
11            
12             $VERSION='0.96';
13             @ISA=qw(Exporter);
14             @EXPORT=qw();
15            
16            
17             # export enum constants
18             my %const;
19 2         616 use constant +{%const=(
20             # ConnectionType enums
21             IAF_CT_LAN => 0,
22             IAF_CT_DIALER => 1,
23             IAF_CT_DIALUP => 2,
24             IAF_CT_IE_DEFAULT => 3,
25             # AuthMethod enums
26             IAF_AM_NONE => 0,
27             IAF_AM_SPA => 1,
28             IAF_AM_USE_INCOMING => 2,
29             IAF_AM_PLAIN => 3,
30             # NNTP PostingFormat enums
31             IAF_PF_USE_OPTIONS => 0,
32             IAF_PF_PLAIN => 1,
33             IAF_PF_HTML => 2,
34 2     2   12 )};
  2         3  
35             push(@EXPORT,keys %const);
36            
37            
38             use constant {
39 2         6340 HEADER => "\x66\x4D\x41\x49\x00\x00\x05\x00\x01\x00\x00\x00",
40             PASSWORD_SEED => "\x75\x18\x15\x14",
41             PASSWORD_HEADER => "\x01\x01",
42             MAX_FIELD_LENGTH => 4096,
43 2     2   11 };
  2         4  
44            
45            
46             # field value regexes
47             my $bool_re=qr/^[01]$/; # boolean
48             my $num_re=qr/^\d+$/; # numeric
49             my $regkey_re=qr/^[0-9a-z]*$/i; # registry key
50            
51             my $iaf_ct_re=qr/^[${\IAF_CT_LAN}-${\IAF_CT_IE_DEFAULT}]$/;
52             my $iaf_am_re=qr/^[${\IAF_AM_NONE}-${\IAF_AM_PLAIN}]$/;
53             my $iaf_pf_re=qr/^[${\IAF_PF_USE_OPTIONS}-${\IAF_PF_HTML}]$/;
54            
55             # field binary formats
56             my $ulong_le_fmt='V'; # an unsigned long in portable little-endian order
57             my $nullstr_fmt='Z*'; # a null terminated string
58            
59            
60             my %fields=(
61             # name # id # binary format # value regex # callback
62             'AccountName' => [305464304, $nullstr_fmt, ],
63             'TemporaryAccount' => [305595369, $ulong_le_fmt, $bool_re, ],
64             'ConnectionType' => [305726441, $ulong_le_fmt, $iaf_ct_re, ],
65             'ConnectionName' => [305791984, $nullstr_fmt, ],
66             'ConnectionFlags' => [305857513, $ulong_le_fmt, $num_re, ],
67             'AccountID' => [305988592, $nullstr_fmt, $regkey_re, ],
68             'BackupConnectionName' => [306054128, $nullstr_fmt, ],
69             'MakeAvailableOffline' => [306185193, $ulong_le_fmt, $bool_re, ],
70             'ServerReadOnly' => [306316277, $ulong_le_fmt, $bool_re, ],
71             'IMAPServer' => [311952368, $nullstr_fmt, ],
72             'IMAPUserName' => [312017904, $nullstr_fmt, ],
73             'IMAPPassword' => [312083446, $nullstr_fmt, '', \&_iaf_password ],
74             'IMAPAuthUseSPA' => [312214517, $ulong_le_fmt, $bool_re, ],
75             'IMAPPort' => [312280041, $ulong_le_fmt, $num_re, ],
76             'IMAPSecureConnection' => [312345589, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
77             'IMAPTimeout' => [312411113, $ulong_le_fmt, $num_re, ],
78             'IMAPRootFolder' => [312476656, $nullstr_fmt, ],
79             'IMAPUseLSUB' => [312673269, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
80             'IMAPPolling' => [312738805, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
81             'IMAPFullList' => [312804341, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
82             'IMAPStoreSpecialFolders' => [313000949, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
83             'IMAPSentItemsFolder' => [313066480, $nullstr_fmt, ],
84             'IMAPDraftsFolder' => [313197552, $nullstr_fmt, ],
85             'IMAPPasswordPrompt' => [313525237, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
86             'IMAPDirty' => [313590761, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
87             'IMAPPollAllFolders' => [313656309, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
88             'HTTPServer' => [321782768, $nullstr_fmt, ],
89             'HTTPUserName' => [321848304, $nullstr_fmt, ],
90             'HTTPPassword' => [321913846, $nullstr_fmt, '', \&_iaf_password ],
91             'HTTPPasswordPrompt' => [321979381, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
92             'HTTPAuthUseSPA' => [322044905, $ulong_le_fmt, $bool_re, ],
93             'HTTPFriendlyName' => [322110448, $nullstr_fmt, ],
94             'DomainIsMSN' => [322175989, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
95             'HTTPPolling' => [322241525, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
96             'AdBarURL' => [322307056, $nullstr_fmt, ],
97             'ShowAdBar' => [322372597, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
98             'MinPollingInterval' => [322438135, $ulong_le_fmt, $num_re, ],
99             'GotPollingInterval' => [322503669, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
100             'LastPolledTime' => [322569207, $ulong_le_fmt, $num_re, ],
101             'NNTPServer' => [325059568, $nullstr_fmt, ],
102             'NNTPUserName' => [325125104, $nullstr_fmt, ],
103             'NNTPPassword' => [325190646, $nullstr_fmt, '', \&_iaf_password ],
104             'NNTPAuthMethod' => [325321717, $ulong_le_fmt, $iaf_am_re, ],
105             'NNTPPort' => [325387241, $ulong_le_fmt, $num_re, ],
106             'NNTPSecureConnection' => [325452789, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
107             'NNTPTimeout' => [325518313, $ulong_le_fmt, $num_re, ],
108             'NNTPDisplayName' => [325583856, $nullstr_fmt, ],
109             'NNTPOrganizationName' => [325649392, $nullstr_fmt, ],
110             'NNTPEmailAddress' => [325714928, $nullstr_fmt, ],
111             'NNTPReplyToEmailAddress' => [325780464, $nullstr_fmt, ],
112             'NNTPSplitMessages' => [325846005, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
113             'NNTPSplitMessageSize' => [325911529, $ulong_le_fmt, $num_re, ],
114             'NNTPUseGroupDescriptions' => [325977077, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
115             'NNTPPolling' => [326108149, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
116             'NNTPPostingFormat' => [326173673, $ulong_le_fmt, $iaf_pf_re, ],
117             'NNTPSignature' => [326239216, $nullstr_fmt, $regkey_re, ],
118             'NNTPPasswordPrompt' => [326304757, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
119             'POP3Server' => [331613168, $nullstr_fmt, ],
120             'POP3UserName' => [331678704, $nullstr_fmt, ],
121             'POP3Password' => [331744246, $nullstr_fmt, '', \&_iaf_password ],
122             'POP3AuthUseSPA' => [331875317, $ulong_le_fmt, $bool_re, ],
123             'POP3Port' => [331940841, $ulong_le_fmt, $num_re, ],
124             'POP3SecureConnection' => [332006389, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
125             'POP3Timeout' => [332071913, $ulong_le_fmt, $num_re, ],
126             'POP3LeaveMailOnServer' => [332137461, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
127             'POP3RemoveWhenDeleted' => [332202997, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
128             'POP3RemoveWhenExpired' => [332268533, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
129             'POP3ExpireDays' => [332334057, $ulong_le_fmt, $num_re, ],
130             'POP3SkipAccount' => [332399605, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
131             'POP3PasswordPrompt' => [332530677, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
132             'SMTPServer' => [338166768, $nullstr_fmt, ],
133             'SMTPUserName' => [338232304, $nullstr_fmt, ],
134             'SMTPPassword' => [338297846, $nullstr_fmt, '', \&_iaf_password ],
135             'SMTPAuthMethod' => [338428905, $ulong_le_fmt, $iaf_am_re, ],
136             'SMTPPort' => [338494441, $ulong_le_fmt, $num_re, ],
137             'SMTPSecureConnection' => [338559989, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
138             'SMTPTimeout' => [338625513, $ulong_le_fmt, $num_re, ],
139             'SMTPDisplayName' => [338691056, $nullstr_fmt, ],
140             'SMTPOrganizationName' => [338756592, $nullstr_fmt, ],
141             'SMTPEmailAddress' => [338822128, $nullstr_fmt, ],
142             'SMTPReplyToEmailAddress' => [338887664, $nullstr_fmt, ],
143             'SMTPSplitMessages' => [338953205, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
144             'SMTPSplitMessageSize' => [339018729, $ulong_le_fmt, $num_re, ],
145             'SMTPSignature' => [339149808, $nullstr_fmt, $regkey_re, ],
146             'SMTPPasswordPrompt' => [339215349, $ulong_le_fmt, $bool_re, \&_iaf_bool ],
147             );
148            
149            
150             sub new {
151 5     5 1 1628 my ($class,%args)=@_;
152 5         11 my $self={};
153 5         30 while (my ($field_name,$field_def)=each %fields) {
154 409 100       1257 next unless exists $args{$field_name};
155 1         3 my $field=delete $args{$field_name};
156 1 50       6 $field=$field_def->[3]->($field,'set') if $field_def->[3]; # call callback() as 'set'
157 1         3 _check_field($field_name,$field);
158 0         0 $self->{"_$field_name"}=$field;
159             }
160 4 100       253 confess('Unknown argument: '.(keys %args)[0]) if scalar keys %args;
161 3         11 bless($self,$class);
162 3         22 return $self;
163             }
164            
165            
166             sub AUTOLOAD {
167 20   100 20   135 my ($self,$field)=($_[0],@_>1 && \$_[1]);
168 20 50       70 confess('Not an object!') unless ref $self;
169 20         24 my $field_name;
170 20         123 ($field_name=$AUTOLOAD)=~s/^.*:://; # trim package name
171 20 50       51 return if $field_name eq 'DESTROY'; # let DESTROY fall through
172 20 100       190 confess("Can't access '$field_name' field in $self") unless exists $fields{$field_name};
173 19         33 my $field_def=$fields{$field_name};
174 19         22 my $new_field;
175 19 100       53 unless (ref $field) { # get
    100          
176 13         43 $new_field=$self->{"_$field_name"};
177 13 100       48 $new_field=$field_def->[3]->($new_field,'get') if $field_def->[3]; # call callback() as 'get'
178             } elsif (defined $$field) { # set
179 5         11 $new_field=$$field;
180 5 100       31 $new_field=$field_def->[3]->($new_field,'set') if $field_def->[3]; # call callback() as 'set'
181 5         13 _check_field($field_name,$new_field);
182 5         17 $self->{"_$field_name"}=$new_field;
183             } else { # delete
184 1         4 $new_field=$self->{"_$field_name"};
185 1 50       6 $new_field=$field_def->[3]->($new_field,'delete') if $field_def->[3]; # call callback() as 'delete'
186 1         4 delete $self->{"_$field_name"};
187             }
188 19         113 return $new_field;
189             }
190            
191            
192             # build a reverse hash for read/write/text operations
193             my %lookup=map {
194             my $field_def=$fields{$_};
195             # id # name # binary format # value regex # callback
196             $field_def->[0], [$_, $field_def->[1], $field_def->[2] || '', $field_def->[3] || '']
197             } keys %fields;
198            
199            
200             sub read_iaf {
201 2   50 2 1 789 my ($self,$data)=($_[0],@_>1 && \$_[1]);
202 2         4 my $pos=0;
203 2         8 my $len=length($$data);
204 2 50       10 confess('Premature end of data while reading header') if $pos+length(HEADER)>$len;
205 2         4 $pos+=length(HEADER); # read header
206             # read fields
207 2         6 while ($pos<$len) {
208 104 50       498 confess('Premature end of data while reading field_id') if $pos+4>$len;
209 104         755 my $field_id=unpack('V',substr($$data,$pos,4));
210 104         222 $pos+=4;
211 104 50       277 confess('Premature end of data while reading field_len') if $pos+4>$len;
212 104         1077 my $field_len=unpack('V',substr($$data,$pos,4));
213 104         187 $pos+=4;
214 104 50       281 confess('Premature end of data while reading field') if $pos+$field_len>$len;
215 104 50       239 confess('Excessive field length: '.$field_len) if $field_len>MAX_FIELD_LENGTH;
216 104         631 my $field=substr($$data,$pos,$field_len);
217 104         166 $pos+=$field_len;
218 104 50       506 confess('Unknown field: '.$field_id) unless exists $lookup{$field_id};
219 104         175 my $field_def=$lookup{$field_id};
220 104 100       299 $field=$field_def->[3]->($field,'read','packed') if $field_def->[3]; # call callback() as 'read packed'
221 104 50       624 $field=unpack($field_def->[1],$field) if $field_def->[1]; # apply binary format
222 104 100       1271 $field=$field_def->[3]->($field,'read','unpacked') if $field_def->[3]; # call callback() as 'read unpacked'
223 104         370 my $field_name=$field_def->[0];
224 104         243 _check_field($field_name,$field);
225 104         2867 $self->{"_$field_name"}=$field;
226             }
227 2         16 return 1;
228             }
229            
230            
231             sub write_iaf {
232 1   50 1 1 11 my ($self,$data)=($_[0],@_>1 && \$_[1]);
233 1         3 $$data=HEADER; # write header
234             # write fields
235 1         11 while (my ($field_id,$field_def)=each %lookup) {
236 85         134 my $field_name=$field_def->[0];
237 85 100       319 next unless exists $self->{"_$field_name"};
238 52         130 my $field=$self->{"_$field_name"};
239 52 100       147 $field=$field_def->[3]->($field,'write','unpacked') if $field_def->[3]; # call callback() as 'write unpacked'
240 52 50       234 $field=pack($field_def->[1],$field) if $field_def->[1]; # apply binary format
241 52 100       139 $field=$field_def->[3]->($field,'write','packed') if $field_def->[3]; # call callback() as 'write packed'
242 52         144 my $field_len=pack('V',length($field));
243 52         108 $field_id=pack('V',$field_id);
244 52         299 $$data.="$field_id$field_len$field";
245             }
246 1         10 return 1;
247             }
248            
249            
250             sub text_iaf {
251 0   0 0 1 0 my ($self,$data,$delimiter)=($_[0],@_>1 && \$_[1],$_[2]);
252 0   0     0 $delimiter||="\t"; # assume 'tab' delimiter
253 0         0 $$data=''; # write header
254             # write sorted fields (name value)
255 0         0 foreach my $field_id (sort keys %lookup) {
256 0         0 my $field_def=$lookup{$field_id};
257 0         0 my $field_name=$field_def->[0];
258 0 0       0 next unless exists $self->{"_$field_name"};
259 0         0 my $field=$self->{"_$field_name"};
260 0 0       0 $field=$field_def->[3]->($field,'text','') if $field_def->[3]; # call callback() as 'text'
261 0         0 $$data.="$field_name$delimiter$field\n";
262             }
263 0         0 return 1;
264             }
265            
266            
267             sub _check_field {
268 110   50 110   428 my ($field_name,$field)=($_[0],@_>1 && \$_[1]);
269 110         775 my $field_def=$fields{$field_name};
270 110 50       338 my $field_re=$field_def->[2] ? ref $field_def->[2] eq 'Regexp' ? $field_def->[2] : qr/$field_def->[2]/ : '';
    100          
271 110 100 66     1152 $$field!~$field_re && confess('Invalid field value: '.$$field) if $field_re;
272             }
273            
274            
275             # turn parameters into boolean 0/1 values
276             sub _iaf_bool {
277 130   50 130   536 my ($value,$operation,$phase)=(@_>0 && \$_[0],$_[1],$_[2]);
278             # this callback runs only during 'get' or 'set' operations
279 130 100 100     805 return $$value unless $operation eq 'get' || $operation eq 'set';
280 4 100       18 return $$value ? 1 : 0;
281             }
282            
283            
284             # encrypt/decrypt passwords
285             sub _iaf_password {
286 15   50 15   74 my ($password,$operation,$phase)=(@_>0 && \$_[0],$_[1],$_[2]);
287             # protect sensitive data
288 15 50       35 return '********' if $operation eq 'text';
289             # this callback runs only during 'read' or 'write' operations
290 15 100 100     78 return $$password unless $operation eq 'read' || $operation eq 'write';
291             # this callback operates only on 'packed' data
292 12 100       39 return $$password unless $phase eq 'packed';
293 6         21 my ($ret,$pos,$len)=('',0,length($$password));
294 6         11 my $seed=PASSWORD_SEED;
295 6         8 my $fill;
296 6 100       15 if ($operation eq 'read') {
297 4 50       13 confess('Premature end of data while reading password header') if $pos+length(PASSWORD_HEADER)>$len;
298 4         6 $pos+=length(PASSWORD_HEADER);
299 4 50       20 confess('Premature end of data while reading password_len') if $pos+4>$len;
300 4         23 my $password_len=unpack('V',substr($$password,$pos,4));
301 4         7 $pos+=4;
302 4 50       16 confess('Malformed password record') if $pos+$password_len!=$len;
303             } else {
304 2         4 $ret=PASSWORD_HEADER;
305 2         10 $ret.=pack('V',$len);
306             }
307 6         19 while ($pos<$len) {
308 9 100       37 $fill=$pos+4>$len ? $pos+4-$len : 0;
309 9         46 $seed=unpack('V',("\x00" x $fill).substr($seed,$fill));
310 9         41 my $d=unpack('V',("\x00" x $fill).substr($$password,$pos,4-$fill));
311 9         22 $pos+=4-$fill;
312 9         35 $ret.=substr(pack('V',$d^$seed),$fill);
313 9 100       58 $seed=pack('V',$operation eq 'read' ? $d^$seed : $d);
314             }
315 6         26 return $ret;
316             }
317            
318             1; # End of Win32::Outlook::IAF
319            
320             __DATA__