File Coverage

lib/Sisimai/SMTP/Transcript.pm
Criterion Covered Total %
statement 71 72 98.6
branch 15 24 62.5
condition 20 24 83.3
subroutine 9 9 100.0
pod 0 1 0.0
total 115 130 88.4


line stmt bran cond sub pod time code
1             package Sisimai::SMTP::Transcript;
2 4     4   4577 use v5.26;
  4         11  
3 4     4   18 use strict;
  4         8  
  4         107  
4 4     4   16 use warnings;
  4         18  
  4         206  
5 4     4   288 use Sisimai::String;
  4         14  
  4         116  
6 4     4   401 use Sisimai::SMTP::Reply;
  4         8  
  4         104  
7 4     4   570 use Sisimai::SMTP::Status;
  4         8  
  4         93  
8 4     4   293 use Sisimai::SMTP::Command;
  4         7  
  4         2918  
9              
10             sub rise {
11             # Decode the transcript of the SMTP session and makes the structured data
12             # @param [String] argv0 A transcript text MTA returned
13             # @param [String] argv1 A label string of a SMTP client
14             # @param [String] argv2 A label string of a SMTP server
15             # @return [Array] Structured data
16             # @return [undef] Failed to decode or the 1st argument is missing
17             # @since v5.0.0
18 12     12 0 718 my $class = shift;
19 12   100     36 my $argv0 = shift // return undef;
20 11   100     28 my $argv1 = shift // '>>>'; # Label for an SMTP Client
21 11   100     24 my $argv2 = shift // '<<<'; # Label for an SMTP Server
22              
23             # 1. Replace label strings of SMTP client/server at the each line
24 11 100       358 $argv0 =~ s/^[ ]+$argv1\s+/>>> /gm; return undef unless index($argv0, '>>> ') > -1;
  11         52  
25 6 50       217 $argv0 =~ s/^[ ]+$argv2\s+/<<< /gm; return undef unless index($argv0, '<<< ') > -1;
  6         25  
26              
27             # 2. Remove strings until the first '<<<' or '>>>'
28 6         9 my $esmtp = [];
29             my $table = sub {
30             return {
31 48     48   305 'command' => undef, # SMTP command
32             'argument' => '', # An argument of each SMTP command sent from a client
33             'parameter' => {}, # Parameter pairs of the SMTP command
34             'response' => { # A Response from an SMTP server
35             'reply' => '', # - SMTP reply code such as 550
36             'status' => '', # - SMTP status such as 5.1.1
37             'text' => [], # - Response text lines
38             }
39             };
40 6         28 };
41              
42 6         12 my $cx = undef; # Current session for $esmtp
43 6         11 my $p1 = index($argv0, '>>>'); # Sent command
44 6         26 my $p2 = index($argv0, '<<<'); # Server response
45 6 50       15 if( $p2 < $p1 ) {
46             # An SMTP server response starting with '<<<' is the first
47 6         23 push @$esmtp, $table->();
48 6         10 $cx = $esmtp->[-1];
49 6         14 $cx->{'command'} = 'CONN';
50 6 50       23 $argv0 = substr($argv0, $p2,) if $p2 > -1;
51              
52             } else {
53             # An SMTP command starting with '>>>' is the first
54 0 0       0 $argv0 = substr($argv0, $p1,) if $p1 > -1;
55             }
56              
57             # 3. Remove unused lines, concatenate folded lines
58 6         21 $argv0 = substr($argv0, 0, index($argv0, "\n\n") - 1); # Remove strings from the first blank line to the tail
59 6         67 $argv0 =~ s/\n[ ]+/ /g; # Concatenate folded lines to each previous line
60              
61 6         49 for my $e ( split("\n", $argv0) ) {
62             # 4. Read each SMTP command and server response
63 162 100       300 if( index($e, '>>> ') == 0 ) {
64             # SMTP client sent a command ">>> SMTP-command arguments"
65 42   50     130 my $thecommand = Sisimai::SMTP::Command->find($e) || next;
66 42         173 my $commandarg = Sisimai::String->sweep(substr($e, index($e, $thecommand) + length($thecommand),));
67 42         64 my $parameters = '';
68              
69 42         65 push @$esmtp, $table->();
70 42         58 $cx = $esmtp->[-1];
71 42         76 $cx->{'command'} = uc $thecommand;
72              
73 42 100 100     200 if( $thecommand eq 'MAIL' || $thecommand eq 'RCPT' || $thecommand eq 'XFORWARD' ) {
      100        
74             # MAIL or RCPT
75 24 100 100     84 if( index($commandarg, 'FROM:') == 0 || index($commandarg, 'TO:') == 0 ) {
76             # >>> MAIL FROM: SIZE=65535
77             # >>> RCPT TO:
78 12         19 my $p4 = index($commandarg, '<');
79 12         26 my $p5 = index($commandarg, '>');
80 12         32 $cx->{'argument'} = substr($commandarg, $p4 + 1, $p5 - $p4 - 1);
81 12         35 $parameters = Sisimai::String->sweep(substr($commandarg, $p5 + 1,));
82              
83             } else {
84             # >>> XFORWARD NAME=neko2-nyaan3.y.example.co.jp ADDR=230.0.113.2 PORT=53672
85             # <<< 250 2.0.0 Ok
86             # >>> XFORWARD PROTO=SMTP HELO=neko2-nyaan3.y.example.co.jp IDENT=2LYC6642BLzFK3MM SOURCE=REMOTE
87             # <<< 250 2.0.0 Ok
88 12         16 $parameters = $commandarg;
89 12         15 $commandarg = '';
90             }
91              
92 24         54 for my $f ( split(" ", $parameters) ) {
93             # SIZE=22022, PROTO=SMTP, and so on
94 54 50 33     155 next if index($f, '=') < 1 || length $f < 3;
95              
96 54 50       87 my @ee = (split('=', $f)); next unless scalar @ee == 2;
  54         75  
97 54         175 $cx->{'parameter'}->{ lc $ee[0] } = $ee[1];
98             }
99             } else {
100             # HELO, EHLO, AUTH, DATA, QUIT or Other SMTP command
101 18         56 $cx->{'argument'} = $commandarg;
102             }
103             } else {
104             # SMTP server sent a response "<<< response text"
105 120 50       134 my $p3 = index($e, '<<< '); next unless $p3 == 0; substr($e, $p3, 4, '');
  120         160  
  120         150  
106              
107 120   50     281 $cx->{'response'}->{'reply'} = Sisimai::SMTP::Reply->find($e) || '';
108 120   100     288 $cx->{'response'}->{'status'} = Sisimai::SMTP::Status->find($e) || '';
109 120         252 push $cx->{'response'}->{'text'}->@*, $e;
110             }
111             }
112 6 50       36 return undef unless scalar @$esmtp;
113 6         47 return $esmtp;
114             }
115              
116             1;
117             __END__