File Coverage

blib/lib/Protocol/IMAP.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             package Protocol::IMAP;
2             # ABSTRACT: Support for RFC3501 Internet Message Access Protocol (IMAP4)
3 4     4   46596 use strict;
  4         9  
  4         131  
4 4     4   19 use warnings;
  4         5  
  4         116  
5 4     4   2007 use parent qw(Mixin::Event::Dispatch);
  4         715  
  4         23  
6              
7             use Encode::IMAPUTF7;
8             use Scalar::Util qw{weaken};
9             use Authen::SASL;
10              
11             use Time::HiRes qw{time};
12             use POSIX qw{strftime};
13              
14             our $VERSION = '0.004';
15              
16             =head1 NAME
17              
18             Protocol::IMAP - support for the Internet Message Access Protocol as defined in RFC3501.
19              
20             =head1 VERSION
21              
22             version 0.004
23              
24             =head1 SYNOPSIS
25              
26             use Protocol::IMAP::Server;
27             use Protocol::IMAP::Client;
28              
29             =head1 DESCRIPTION
30              
31             Base class for L and L implementations.
32              
33             =head1 METHODS
34              
35             =cut
36              
37             # Build up an enumerated list of states. These are defined in the RFC and are used to indicate what we expect to send / receive at client and server ends.
38             our %VALID_STATES;
39             our %STATE_BY_ID;
40             our %STATE_BY_NAME;
41             BEGIN {
42             our @STATES = qw{
43             ConnectionClosed ConnectionEstablished
44             ServerGreeting
45             NotAuthenticated Authenticated
46             Selected
47             Logout
48             };
49             %VALID_STATES = map { $_ => 1 } @STATES;
50             my $state_id = 0;
51             foreach (@STATES) {
52             my $id = $state_id++;
53             $STATE_BY_ID{$id} = $_;
54             { no strict 'refs'; *{__PACKAGE__ . '::' . $_} = sub () { $id } }
55             }
56             %STATE_BY_NAME = reverse %STATE_BY_ID;
57              
58             # Convert from ConnectionClosed to on_connection_closed, etc.
59             my @handlers = sort values %STATE_BY_ID;
60             @handlers = map {;
61             my $v = "on$_";
62             $v =~ s/([A-Z])/'_' . lc($1)/ge;
63             $v
64             } @handlers;
65             { no strict 'refs'; *{__PACKAGE__ . "::STATE_HANDLERS"} = sub () { @handlers } }
66             }
67              
68             sub new {
69             my $class = shift;
70             bless { @_ }, $class
71             }
72              
73             =head2 C
74              
75             Debug log message. Only displayed if the debug flag was passed to L.
76              
77             =cut
78              
79             sub debug {
80             my $self = shift;
81             return $self unless $self->{debug};
82              
83             my $now = Time::HiRes::time;
84             warn strftime("%Y-%m-%d %H:%M:%S", gmtime($now)) . sprintf(".%03d", int($now * 1000.0) % 1000.0) . " @_\n";
85             return $self;
86             }
87              
88             =head2 C
89              
90             Sets or retrieves the current state, in text format.
91              
92             =cut
93              
94             sub state {
95             my $self = shift;
96             if(@_) {
97             my $name = shift;
98             die "Invalid state [$name]" unless defined(my $state_id = $STATE_BY_NAME{$name});
99             return $self->state_id($state_id, @_);
100             }
101             return $STATE_BY_ID{$self->{state_id}};
102             }
103              
104             =head2 state_id
105              
106             Sets or returns the state, in numeric format.
107              
108             =cut
109              
110             sub state_id {
111             my $self = shift;
112             if(@_) {
113             my $state_id = shift;
114             die "Invalid state ID [$state_id]" unless exists $STATE_BY_ID{$state_id};
115             $self->{state_id} = $state_id;
116             $self->debug("State changed to " . $state_id . " (" . $STATE_BY_ID{$state_id} . ")");
117             $self->invoke_event(state => $STATE_BY_ID{$state_id});
118             $self->invoke_event(authenticated => ) if $state_id == $STATE_BY_NAME{Authenticated};
119             # ConnectionEstablished => on_connection_established
120             my $method = 'on' . $STATE_BY_ID{$state_id};
121             $method =~ s/([A-Z])/'_' . lc($1)/ge;
122             if($self->{$method}) {
123             $self->debug("Trying method for [$method]");
124             # If the override returns false, skip the main function
125             return $self unless $self->{$method}->(@_);
126             }
127             $self->$method(@_) if $self->can($method);
128             return $self;
129             }
130             return $self->{state_id};
131             }
132              
133             =head2 in_state
134              
135             Returns true if we're in the given state.
136              
137             =cut
138              
139             sub in_state {
140             my $self = shift;
141             my $expect = shift;
142             die "Invalid state $expect" unless exists $VALID_STATES{$expect};
143             return +($self->state eq $expect) ? 1 : 0;
144             }
145              
146             =head2 C
147              
148             Raise an error if we call ->write at top level, just in case someone's trying to use this directly.
149              
150             =cut
151              
152             sub write {
153             my $self = shift;
154             $self->invoke_event(write => @_);
155             }
156              
157             =head2 C<_capture_weakself>
158              
159             Helper method to avoid capturing $self in closures, using the same approach and method name
160             as in L.
161              
162             =cut
163              
164             sub _capture_weakself {
165             my ($self, $code) = @_;
166              
167             Scalar::Util::weaken($self);
168              
169             return sub {
170             $self->$code(@_)
171             };
172             }
173              
174             1;
175              
176             __END__