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   2876 use v5.26;
  4         10  
3 4     4   31 use strict;
  4         6  
  4         92  
4 4     4   14 use warnings;
  4         6  
  4         246  
5 4     4   315 use Sisimai::String;
  4         6  
  4         94  
6 4     4   420 use Sisimai::SMTP::Reply;
  4         6  
  4         67  
7 4     4   472 use Sisimai::SMTP::Status;
  4         5  
  4         93  
8 4     4   303 use Sisimai::SMTP::Command;
  4         6  
  4         2397  
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 731 my $class = shift;
19 12   100     35 my $argv0 = shift // return undef;
20 11   100     27 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       319 $argv0 =~ s/^[ ]+$argv1\s+/>>> /gm; return undef unless index($argv0, '>>> ') > -1;
  11         50  
25 6 50       160 $argv0 =~ s/^[ ]+$argv2\s+/<<< /gm; return undef unless index($argv0, '<<< ') > -1;
  6         16  
26              
27             # 2. Remove strings until the first '<<<' or '>>>'
28 6         23 my $esmtp = [];
29             my $table = sub {
30             return {
31 48     48   276 '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         24 };
41              
42 6         9 my $cx = undef; # Current session for $esmtp
43 6         17 my $p1 = index($argv0, '>>>'); # Sent command
44 6         12 my $p2 = index($argv0, '<<<'); # Server response
45 6 50       34 if( $p2 < $p1 ) {
46             # An SMTP server response starting with '<<<' is the first
47 6         13 push @$esmtp, $table->();
48 6         11 $cx = $esmtp->[-1];
49 6         13 $cx->{'command'} = 'CONN';
50 6 50       21 $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         55 $argv0 =~ s/\n[ ]+/ /g; # Concatenate folded lines to each previous line
60              
61 6         36 for my $e ( split("\n", $argv0) ) {
62             # 4. Read each SMTP command and server response
63 162 100       253 if( index($e, '>>> ') == 0 ) {
64             # SMTP client sent a command ">>> SMTP-command arguments"
65 42   50     110 my $thecommand = Sisimai::SMTP::Command->find($e) || next;
66 42         181 my $commandarg = Sisimai::String->sweep(substr($e, index($e, $thecommand) + length($thecommand),));
67 42         62 my $parameters = '';
68              
69 42         57 push @$esmtp, $table->();
70 42         51 $cx = $esmtp->[-1];
71 42         63 $cx->{'command'} = uc $thecommand;
72              
73 42 100 100     140 if( $thecommand eq 'MAIL' || $thecommand eq 'RCPT' || $thecommand eq 'XFORWARD' ) {
      100        
74             # MAIL or RCPT
75 24 100 100     96 if( index($commandarg, 'FROM:') == 0 || index($commandarg, 'TO:') == 0 ) {
76             # >>> MAIL FROM: SIZE=65535
77             # >>> RCPT TO:
78 12         28 my $p4 = index($commandarg, '<');
79 12         11 my $p5 = index($commandarg, '>');
80 12         24 $cx->{'argument'} = substr($commandarg, $p4 + 1, $p5 - $p4 - 1);
81 12         16 $parameters = 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         14 $parameters = $commandarg;
89 12         13 $commandarg = '';
90             }
91              
92 24         55 for my $f ( split(" ", $parameters) ) {
93             # SIZE=22022, PROTO=SMTP, and so on
94 54 50 33     144 next if index($f, '=') < 1 || length $f < 3;
95              
96 54 50       92 my @ee = (split('=', $f)); next unless scalar @ee == 2;
  54         83  
97 54         133 $cx->{'parameter'}->{ lc $ee[0] } = $ee[1];
98             }
99             } else {
100             # HELO, EHLO, AUTH, DATA, QUIT or Other SMTP command
101 18         30 $cx->{'argument'} = $commandarg;
102             }
103             } else {
104             # SMTP server sent a response "<<< response text"
105 120 50       121 my $p3 = index($e, '<<< '); next unless $p3 == 0; substr($e, $p3, 4, '');
  120         136  
  120         142  
106              
107 120   50     241 $cx->{'response'}->{'reply'} = Sisimai::SMTP::Reply->find($e) || '';
108 120   100     225 $cx->{'response'}->{'status'} = Sisimai::SMTP::Status->find($e) || '';
109 120         221 push $cx->{'response'}->{'text'}->@*, $e;
110             }
111             }
112 6 50       35 return undef unless scalar @$esmtp;
113 6         49 return $esmtp;
114             }
115              
116             1;
117             __END__