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