File Coverage

blib/lib/Mail/Postfix/Attr.pm
Criterion Covered Total %
statement 88 96 91.6
branch 10 24 41.6
condition 1 3 33.3
subroutine 18 19 94.7
pod 4 16 25.0
total 121 158 76.5


line stmt bran cond sub pod time code
1 4     4   1900 use strict;
  4         4  
  4         96  
2 4     4   16 use warnings;
  4         4  
  4         252  
3             package Mail::Postfix::Attr;
4             {
5             $Mail::Postfix::Attr::VERSION = '0.06';
6             }
7             # ABSTRACT: encode and decode Postfix attributes
8              
9 4     4   20 use Carp;
  4         8  
  4         5052  
10              
11              
12             # I do not understand the terminal slash in \0/
13             # -- rjbs, 2013-01-02
14             my %codecs = (
15             '0' => [ \&encode_0, \&decode_0, q(\0/) ],
16             '64' => [ \&encode_64, \&decode_64, q(\n) ],
17             'plain' => [ \&encode_plain, \&decode_plain, q(\n) ],
18             );
19              
20             sub new {
21 6     6 1 6014949 my ($class, %args) = @_;
22 6         223 my $self = bless {}, $class;
23 6   33     315 my $codec_ref = $codecs{ $args{codec} } || $codecs{plain};
24              
25 6         107 $self->{sock_path} = $args{path};
26 6         99 $self->{inet} = $args{inet};
27 6         74 $self->{fh} = $args{fh};
28              
29 6         36 @{$self}{qw(encode decode delimiter)} = @{$codec_ref};
  6         219  
  6         27  
30              
31 6         132 return $self;
32             }
33              
34             sub fh {
35 12     12 0 21 my $self = shift;
36 12         23 my $fh;
37 12 100       56 unless ($self->{fh}) {
38 6 50       64 if ( $self->{sock_path} ) {
    0          
    0          
39 6         182 require IO::Socket::UNIX;
40 6         184 $fh = IO::Socket::UNIX->new( $self->{sock_path} );
41 6 50       3627 $fh or croak "Mail::Postfix::Attr can't connect to '$self->{sock_path}' $!\n";
42 6         54 $self->{fh} = $fh;
43             } elsif ( $self->{inet} ) {
44 0         0 require IO::Socket::INET;
45 0         0 $fh = IO::Socket::INET->new( $self->{inet} );
46 0 0       0 $fh or croak "Mail::Postfix::Attr can't connect to '$self->{inet}' $!\n";
47 0         0 $self->{fh} = $fh;
48             } elsif ($self->{fh}) {
49 0         0 $fh = $self->{fh}
50             } else {
51 0         0 croak "must have 'path' or 'inet' or 'fh' set to use send";
52             }
53             }
54 12 50       73 croak "can't find filehandle for $self" unless $self->{fh};
55 12         35 return $self->{fh};
56             }
57              
58              
59             sub send {
60 6     6 1 270 my ($self) = shift;
61 6         105 $self->write(@_);
62 6         31 return $self->read;
63             }
64              
65             sub write {
66 6     6 0 57 my ($self) = shift;
67 6         160 $self->raw_write($self->encode(@_));
68             }
69              
70             sub raw_write {
71 6     6 0 17 my $self = shift;
72 6         64 my $fh = $self->fh;
73 6         108 my $count = syswrite($fh, shift);
74 6 50       38 croak "syswrite: error: $!" unless $count;
75             }
76              
77             sub read {
78 6     6 0 63 my ($self) = shift;
79 6         22 return map @$_, $self->decode($self->raw_read);
80             }
81              
82             sub raw_read {
83 6     6 0 15 my $self = shift;
84 6         14 my $fh = $self->fh;
85 6         17 my $buf;
86 6         9427 my $r = sysread($fh, $buf, 64000);
87 6 50       38 die "sysread error: $!" unless defined $r;
88 6         52 return $buf;
89             }
90              
91              
92             sub encode {
93 6     6 1 15 my ($self) = @_;
94 6         180 goto $self->{encode};
95             }
96              
97              
98             sub decode {
99 6     6 1 192 my ($self) = @_;
100 6         128 goto $self->{decode};
101             }
102              
103             sub delimiter {
104 0     0 0 0 my ($self) = @_;
105 0         0 return $self->{delimiter};
106             }
107              
108             sub encode_0 {
109 3     3 0 45 my ($self) = shift;
110 3         42 my $attr_text;
111 3         138 while (my ($attr, $val) = splice(@_, 0, 2)) {
112 24 50       270 $val = "" unless defined $val;
113 24         405 $attr_text .= "$attr\0$val\0";
114             }
115 3         75 return "$attr_text\0";
116             }
117              
118             sub encode_64 {
119 2     2 0 36 my ($self) = shift;
120 2         30 my $attr_text;
121 2         4316 require MIME::Base64;
122 2         3292 while (my ($attr, $val) = splice(@_, 0, 2)) {
123 16 50       40 $val = "" unless defined $val;
124 16         160 $attr_text .= MIME::Base64::encode_base64( $attr, '' ) . ':' .
125             MIME::Base64::encode_base64( $val, '' ) . "\n";
126             }
127 2         18 return "$attr_text\n";
128             }
129              
130             sub encode_plain {
131 1     1 0 18 my ($self) = shift;
132 1         11 my $attr_text;
133 1         23 while (my ($attr, $val) = splice(@_, 0, 2)) {
134 8 50       37 $val = "" unless defined $val;
135 8         46 $attr_text .= "$attr=$val\n";
136             }
137 1         12 return "$attr_text\n";
138             }
139              
140             sub decode_0 {
141 3     3 0 21 my ($self, $text) = @_;
142 3         9 my @attrs;
143             # the lookahead avoids a situation where (x => "") is
144             # encoded as "x\0\0\0" but then decoded into [ "x" ], []
145 3         153 foreach my $section ( split /(?<=\0\0)(?!\0)/, $text ) {
146             # count is here to make sure that trailing attributes with empty values are correctly given
147             # previously (a => 1, b => 2, c => "") would come out as [ "a", 1, "b", 2, "c" ]
148 3         15 my $count = ($section =~ tr/\0//) - 1;
149 3         18 $section = substr($section, 0, length($section) - 2);
150 3         60 push( @attrs, [ split /\0/, $section, $count ] );
151             }
152 3         60 return @attrs;
153             }
154              
155             sub decode_64 {
156 2     2 0 6 my ($self, $text) = @_;
157 2         16 require MIME::Base64;
158 2         20 my @attrs;
159 2         72 foreach my $section (split /(?<=\n\n)/, $text) {
160 2         188 push (@attrs, [ map MIME::Base64::decode_base64 $_,
161             $section =~ /^([^:]+):(.+)$/mg ]);
162             }
163 2         28 return @attrs;
164             }
165              
166             sub decode_plain {
167 1     1 0 10 my ($self, $text) = @_;
168 1         8 my @attrs;
169 1         52 foreach my $section (split /(?<=\n\n)/, $text) {
170 1         32 push (@attrs, [ map { split /=/, $_, 2 } split /\n/, $section ]);
  8         49  
171             }
172 1         19 return @attrs;
173             }
174              
175             1;
176              
177             __END__