File Coverage

blib/lib/Net/Gmail/IMAP/Label/Proxy.pm
Criterion Covered Total %
statement 66 124 53.2
branch 8 42 19.0
condition 0 4 0.0
subroutine 14 30 46.6
pod 0 7 0.0
total 88 207 42.5


line stmt bran cond sub pod time code
1             package Net::Gmail::IMAP::Label::Proxy;
2             # ABSTRACT: Implementation of proxy logic for FETCH X-GM-LABELS
3             $Net::Gmail::IMAP::Label::Proxy::VERSION = '0.008';
4 4     4   110604 use warnings;
  4         21  
  4         99  
5 4     4   28 use strict;
  4         12  
  4         114  
6 4         23 use POE qw(Component::Server::TCP Component::Client::TCP
7 4     4   1948 Filter::Stackable Filter::Map);
  4         124424  
8 4     4   279597 use POE::Component::SSLify qw( Client_SSLify );
  4         53416  
  4         226  
9 4     4   2042 use Regexp::Common;
  4         13562  
  4         13  
10 4     4   508532 use Encode::IMAPUTF7;
  4         46530  
  4         129  
11 4     4   24 use Encode qw/decode encode_utf8/;
  4         7  
  4         298  
12 4     4   22 use Carp;
  4         8  
  4         241  
13              
14 4     4   21 use constant DEFAULT_LOCALPORT => 10143;
  4         8  
  4         216  
15 4     4   20 use constant LINESEP => "\x0D\x0A";
  4         8  
  4         168  
16 4     4   18 use constant GMAIL_HOST => 'imap.gmail.com';
  4         7  
  4         177  
17 4     4   20 use constant GMAIL_PORT => 993; # IMAPS port
  4         9  
  4         6307  
18              
19             # options
20             # * localport : (0..65535) - port to start local side of proxy
21             # * verbose : (0..4) - logging level
22             sub new {
23 0     0 0 0 my $class = shift;
24 0 0       0 ref($class) and croak "class name needed";
25 0         0 my %opts = @_;
26 0         0 my $self = {};
27 0         0 bless $self, $class;
28 0   0     0 $self->{verbose} = $opts{verbose} // 0;
29 0   0     0 $self->{localport} = $opts{localport} // DEFAULT_LOCALPORT;
30 0         0 $self;
31             }
32              
33             sub run {
34 0     0 0 0 my ($self) = @_;
35 0 0       0 $self->init() unless $self->{_init};
36 0 0       0 $self->{verbose} and carp 'running';
37 0         0 $poe_kernel->run();
38             }
39              
40             # Spawn the forwarder server on port given in by localport option. When new
41             # connections arrive, spawn clients to connect them to their destination.
42             sub init {
43 0     0 0 0 my ($self) = @_;
44             POE::Component::Server::TCP->new(
45             Port => $self->{localport},
46             ClientConnected => sub {
47 0     0   0 my ($heap, $session) = @_[HEAP, SESSION];
48 0 0       0 $self->{verbose} > 0 and logevent('server got connection', $session);
49 0         0 $heap->{client_id} = $self->spawn_client_side();
50             },
51             ClientFilter => POE::Filter::Stackable->new(
52             Filters => [
53             POE::Filter::Line->new( Literal => LINESEP),
54             POE::Filter::Map->new( Get => \&get_label, Put => \&put_label ),
55             ]),
56             ClientInput => sub {
57 0     0   0 my ($kernel, $session, $heap, $input) = @_[KERNEL, SESSION, HEAP, ARG0];
58 0 0       0 $self->{verbose} > 2 and logevent('server got input', $session, $self->{verbose} > 3 ? $input : undef);
    0          
59 0         0 $kernel->post($heap->{client_id} => send_stuff => $input);
60             },
61             ClientDisconnected => sub {
62 0     0   0 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
63 0 0       0 $self->{verbose} > 0 and logevent('server got disconnect', $session);
64 0         0 $kernel->post($heap->{client_id} => "shutdown");
65             },
66             InlineStates => {
67             send_stuff => sub {
68 0     0   0 my ($heap, $stuff) = @_[HEAP, ARG0];
69 0 0       0 $self->{verbose} > 2 and logevent("sending to server", $_[SESSION], $self->{verbose} > 3 ? $stuff : undef );
    0          
70 0         0 eval { $heap->{client}->put($stuff); };
  0         0  
71             },
72             },
73 0         0 );
74 0         0 $self->{_init} = 1; # set init flag
75             }
76              
77             sub spawn_client_side {
78 0     0 0 0 my ($self) = @_;
79             POE::Component::Client::TCP->new(
80             RemoteAddress => GMAIL_HOST,
81             PreConnect => sub {
82             # Convert the socket into an SSL socket.
83 0     0   0 my $socket = eval { Client_SSLify($_[ARG0]) };
  0         0  
84 0 0       0 return if $@; # Disconnect if SSL failed.
85 0         0 return $socket;
86             },
87             RemotePort => GMAIL_PORT, # IMAPS port
88             Filter => POE::Filter::Line->new( Literal => LINESEP),
89             Started => sub {
90 0     0   0 $_[HEAP]->{server_id} = $_[SENDER]->ID;
91             },
92             Connected => sub {
93 0     0   0 my ($heap, $session) = @_[HEAP, SESSION];
94 0 0       0 $self->{verbose} > 0 and logevent('client connected', $session);
95 0         0 eval { $heap->{server}->put(''); };
  0         0  
96             },
97             ServerInput => sub {
98 0     0   0 my ($kernel, $heap, $session, $input) = @_[KERNEL, HEAP, SESSION, ARG0];
99 0 0       0 $self->{verbose} > 1 and logevent('client got input', $session, $self->{verbose} > 2 ? $input : undef);
    0          
100             # TODO: check capabilities?
101 0         0 $kernel->post($heap->{server_id} => send_stuff => $input);
102             },
103             Disconnected => sub {
104 0     0   0 my ($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION];
105 0 0       0 $self->{verbose} > 0 and logevent('client disconnected', $session);
106 0         0 $kernel->post($heap->{server_id} => 'shutdown');
107             },
108             ConnectError => sub {
109 0     0   0 my ($operation, $error_number, $error_string) = @_[ARG0..ARG2];
110 0         0 my $id = $_[SESSION]->ID;
111 0         0 print STDERR "Client $id: $operation error $error_number occurred: $error_string\n";
112 0         0 $_[KERNEL]->post($_[HEAP]->{server_id} => 'shutdown');
113             },
114             InlineStates => {
115             send_stuff => sub {
116 0     0   0 my ($heap, $stuff) = @_[HEAP, ARG0];
117 0 0       0 $self->{verbose} > 2 and logevent("sending to client", $_[SESSION], $self->{verbose} > 3 ? $stuff : undef);
    0          
118 0         0 eval { $heap->{server}->put($stuff); };
  0         0  
119             },
120             },
121 0         0 );
122             }
123              
124             sub logevent {
125 0     0 0 0 my ($state, $session, $arg) = @_;
126 0         0 my $id = $session->ID();
127 0         0 print "session $id $state ";
128 0 0       0 print ": $arg" if (defined $arg);
129 0         0 print "\n";
130             }
131              
132             sub get_label {
133 4     4 0 7307 my $data = shift;
134 4 100       29 if($data =~ /^\w+ FETCH/) {
    100          
135 1         10 $data =~ s,(BODY\.PEEK\[[^\]]*\]),$1 X-GM-LABELS,;
136             } elsif($data =~ /^\w+ UID FETCH (\d+) \(?BODY.PEEK\[\]\)?$/) {
137 2         14 $data =~ s,\(?(BODY.PEEK\[\])\)?,($1 X-GM-LABELS),;
138             }
139 4         14 return $data;
140             }
141              
142             sub put_label {
143 16     16 0 46326 my $data = shift;
144 16         62 my $fetch_re = qr/^\* \d+ FETCH.*{\d+}$/;
145 16         95 my $label_re = qr/(?:[^() "]+)|$RE{delimited}{-delim=>'"'}/;
146 16         3076 my $fetch_gm_label = qr/^(\* \d+ FETCH.*)(X-GM-LABELS \((?:(?:$label_re\s+)*$label_re)?\) ?)(.*)\{(\d+)\}$/;
147 16 100       250 if( $data =~ $fetch_gm_label ) {
148 14         75 my $octets = $4;
149 14         62 my $new_fetch = "$1$3";
150             #print "$new_fetch\n";
151 14         74 (my $x_label = $2) =~ /\((.*)\)/;
152 14         40 $x_label = $1;
153 14         42 $x_label =~ s,"\\\\Important"\s*,,;
154 14         34 $x_label =~ s,"\\\\Sent"\s*,,;
155 14         34 $x_label =~ s,"\\\\Starred"\s*,,;
156 14         28 $x_label =~ s,"\\\\Inbox",INBOX,;
157 14         31 $x_label =~ s,^\s+,,; $x_label =~ s,\s+$,,; # trim
  14         42  
158             # Gmail sends IMAP's modified UTF-7,
159             # need to convert to UTF-8 to satisfy
160             # in mutt
161 14         51 $x_label = decode('IMAP-UTF-7', $x_label);
162 14 100       859 if(length($x_label) > 0) {
163 11         30 $x_label = "X-Label: $x_label";
164             #print "$x_label\n";
165 11         33 $octets += length(encode_utf8($x_label))+length(LINESEP); # 2 more for line separator
166 11         134 $new_fetch .= "{$octets}";
167 11         21 $new_fetch .= LINESEP;
168 11         22 $new_fetch .= $x_label;
169             } else {
170 3         14 $new_fetch .= "{$octets}";
171             }
172 14         92 return $new_fetch;
173             }
174 2         11 return $data;
175             }
176              
177             1;
178              
179             # vim:ts=4:sw=4
180              
181             __END__