File Coverage

lib/Qmail/Deliverable/Client.pm
Criterion Covered Total %
statement 68 68 100.0
branch 31 36 86.1
condition 3 3 100.0
subroutine 11 11 100.0
pod 2 2 100.0
total 115 120 95.8


line stmt bran cond sub pod time code
1             package Qmail::Deliverable::Client;
2              
3 5     5   4144 use strict;
  5         11  
  5         185  
4 5     5   83 use 5.006;
  5         17  
5 5     5   26 use Carp qw(carp);
  5         21  
  5         319  
6 5     5   30 use base 'Exporter';
  5         5  
  5         728  
7 5     5   1699 use IO::Socket::INET;
  5         79308  
  5         75  
8 5     5   3342 use Qmail::Deliverable::Status qw(:status);
  5         9  
  5         7340  
9              
10             our @EXPORT_OK = ( qw(qmail_local deliverable), @Qmail::Deliverable::Status::STATUS, );
11             our %EXPORT_TAGS = (
12             all => \@EXPORT_OK,
13             status => \@Qmail::Deliverable::Status::STATUS,
14             );
15              
16             our $SERVER = "127.0.0.1:8998";
17             our $ERROR;
18              
19             # rfc2822's "atext"
20             my $atext = "[A-Za-z0-9!#\$%&\'*+\/=?^_\`{|}~-]";
21             my $valid = qr/^(?!.*\@.*\@)($atext+(?:[\@.]$atext+)*)\.?\z/;
22              
23             sub _uri_escape {
24 10     10   24 my ($value) = @_;
25 10         81 $value =~ s/([^A-Za-z0-9\-\._~])/sprintf("%%%02X", ord($1))/eg;
  12         126  
26 10         48 return $value;
27             }
28              
29             sub _http_request {
30 13     13   45 my ( $server, $command, $arg ) = @_;
31 13 100       135 my ( $host, $port ) = $server =~ /^([A-Za-z0-9_.-]+):([0-9]+)\z/
32             or return ( undef, undef, "invalid server address" );
33              
34 12 100       154 my $sock = IO::Socket::INET->new(
35             PeerAddr => $host,
36             PeerPort => $port,
37             Proto => 'tcp',
38             Timeout => 5,
39             ) or return ( undef, undef, $! );
40              
41 10         8648 my $request = join "",
42             "GET /qd1/$command?" . _uri_escape($arg) . " HTTP/1.0\r\n",
43             "Host: $host:$port\r\n",
44             "Connection: close\r\n",
45             "\r\n";
46              
47 10 50       17 print {$sock} $request or return ( undef, undef, $! );
  10         667  
48              
49 10         31 my $response = do { local $/; <$sock> };
  10         83  
  10         11937  
50 10         807 close $sock;
51 10 50       52 return ( undef, undef, "empty response" ) if not defined $response;
52              
53 10         205 my ( $headers, $body ) = split /\r?\n\r?\n/, $response, 2;
54 10 100       44 return ( undef, undef, "malformed response" ) if not defined $body;
55              
56 9         176 my ($status_line) = split /\r?\n/, $headers, 2;
57 9 50       74 my ($code) = $status_line =~ /^HTTP\/\d+\.\d+\s+([0-9]+)\b/
58             or return ( undef, undef, "malformed response" );
59              
60 9         93 return ( $code, $body, $status_line );
61             }
62              
63             sub _remote {
64 15     15   61 my ( $command, $arg ) = @_;
65              
66 15 100       59 my $server =
67             ref($SERVER) eq 'CODE'
68             ? $SERVER->()
69             : $SERVER;
70              
71 15 100       49 if ( not defined $server ) {
72 2         5 $ERROR = "No SERVER defined; connection not attempted";
73 2         20 return "\0";
74             }
75              
76 13         27 my ( $code, $body, $sl ) = _http_request( $server, $command, $arg );
77 13 100       1642 if ( not defined $code ) {
78 4         1634 carp $ERROR = "Server $server unreachable or broken! ($sl)";
79 4         50 return "\0";
80             }
81 9 100       46 return undef if $code == 204; # rpc undef
82 8 100       31 if ( $code == 200 ) {
83 7         22 return $body;
84             }
85              
86 1         494 carp $ERROR = "Server $server unreachable or broken! ($sl)";
87 1         28 return "\0";
88             }
89              
90             sub qmail_local {
91 8     8 1 452020 my ($in) = @_;
92             my ($address) = lc($in) =~ /$valid/
93 8 100       164 or do { carp "Invalid address: $in"; return; };
  1         127  
  1         9  
94              
95             # This we can do locally. Let's not waste HTTP requests :)
96 7 100       66 return $address if $address !~ /\@/;
97              
98 6         20 my $rv = _remote 'qmail_local', $address;
99 6 100 100     48 return "" if defined $rv and $rv eq "\0";
100 5         58 return $rv;
101             }
102              
103             sub deliverable {
104 10     10 1 50877 my ($in) = @_;
105             my ($address) = lc($in) =~ /$valid/
106 10 100       368 or do { carp "Invalid address: $in"; return; };
  1         209  
  1         18  
107              
108 9         49 my $rv = _remote 'deliverable', $address;
109 9 50       52 return QD_CLIENT_FAILURE if not defined $rv; # shouldn't happen
110 9 50       20 return QD_CLIENT_FAILURE if not length $rv; # shouldn't happen
111 9 100       89 return QD_CLIENT_FAILURE if $rv eq "\0";
112              
113 3         43 return $rv;
114             }
115              
116             1;
117              
118             __END__