1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Artack\Dsn\Parser; |
6
|
|
|
|
7
|
|
|
use Artack\Dsn\DeliveryStatus; |
8
|
|
|
use Artack\Dsn\Field\ActionField; |
9
|
|
|
use Artack\Dsn\Field\AddressTypeField; |
10
|
|
|
use Artack\Dsn\Field\DateField; |
11
|
|
|
use Artack\Dsn\Field\DiagnosticTypeField; |
12
|
|
|
use Artack\Dsn\Field\MtaNameTypeField; |
13
|
|
|
use Artack\Dsn\Field\StatusField; |
14
|
|
|
use Artack\Dsn\Field\ValueField; |
15
|
|
|
use Artack\Dsn\Fields; |
16
|
|
|
|
17
|
|
|
class DsnParser |
18
|
|
|
{ |
19
|
|
|
private static $map = [ |
20
|
|
|
// 'original-envelope-id' => null, |
21
|
|
|
'reporting-mta' => MtaNameTypeField::class, |
22
|
|
|
'dsn-gateway' => MtaNameTypeField::class, |
23
|
|
|
'received-from-mta' => MtaNameTypeField::class, |
24
|
|
|
'arrival-date' => DateField::class, |
25
|
|
|
'original-recipient' => AddressTypeField::class, |
26
|
|
|
'final-recipient' => AddressTypeField::class, |
27
|
|
|
'action' => ActionField::class, |
28
|
|
|
'status' => StatusField::class, |
29
|
|
|
'remote-mta' => MtaNameTypeField::class, |
30
|
|
|
'diagnostic-code' => DiagnosticTypeField::class, |
31
|
|
|
'last-attempt-date' => DateField::class, |
32
|
|
|
// 'final-log-id' => null, |
33
|
|
|
'will-retry-until' => DateField::class, |
34
|
|
|
]; |
35
|
|
|
|
36
|
12 |
|
public function parse(string $content): DeliveryStatus |
37
|
|
|
{ |
38
|
12 |
|
$deliveryStatus = new DeliveryStatus(); |
39
|
|
|
|
40
|
12 |
|
$groups = explode("\n\n", $content); |
41
|
|
|
|
42
|
12 |
|
$messageFieldGroup = array_shift($groups); |
43
|
12 |
|
$deliveryStatus->setMessageFields($this->parseBlock($messageFieldGroup)); |
44
|
|
|
|
45
|
8 |
|
foreach ($groups as $group) { |
46
|
8 |
|
$deliveryStatus->addRecipientFields($this->parseBlock($group)); |
47
|
|
|
} |
48
|
|
|
|
49
|
8 |
|
return $deliveryStatus; |
50
|
|
|
} |
51
|
|
|
|
52
|
12 |
|
private function parseBlock(string $block): Fields |
53
|
|
|
{ |
54
|
12 |
|
$blockFields = new Fields(); |
55
|
12 |
|
$lines = explode("\n", trim($block)); |
56
|
|
|
|
57
|
12 |
|
foreach ($lines as $key => $line) { |
58
|
12 |
|
if (false === strpos($line, ':')) { |
59
|
6 |
|
$lines[$key - 1] .= $line; |
60
|
4 |
|
unset($lines[$key]); |
61
|
|
|
} |
62
|
|
|
} |
63
|
|
|
|
64
|
10 |
|
$block = implode("\n", $lines); |
65
|
10 |
|
preg_match_all('/^(.*?):\s((.*?);)?(.*?)(\s\((.*)\))?$/m', $block, $matches, PREG_SET_ORDER); |
66
|
|
|
|
67
|
10 |
|
foreach ($matches as $match) { |
68
|
10 |
|
$name = $match[1]; |
69
|
10 |
|
$type = $match[3] ?? null; |
70
|
10 |
|
$value = trim($match[4]); |
71
|
10 |
|
$comment = $match[6] ?? null; |
72
|
|
|
|
73
|
10 |
|
$class = self::$map[strtolower($name)] ?? null; |
74
|
10 |
|
switch ($class) { |
75
|
|
|
case DateField::class: |
76
|
4 |
|
$header = new DateField($name, new \DateTimeImmutable($value)); |
77
|
4 |
|
break; |
78
|
|
|
case ActionField::class: |
79
|
8 |
|
$header = new ActionField($name, $value); |
80
|
8 |
|
break; |
81
|
|
|
case DiagnosticTypeField::class: |
82
|
6 |
|
$header = new DiagnosticTypeField($name, $value, $type, $comment); |
83
|
6 |
|
break; |
84
|
|
|
case AddressTypeField::class: |
85
|
8 |
|
$header = new AddressTypeField($name, $value, $type, $comment); |
86
|
8 |
|
break; |
87
|
|
|
case MtaNameTypeField::class: |
88
|
10 |
|
$header = new MtaNameTypeField($name, $value, $type, $comment); |
89
|
8 |
|
break; |
90
|
|
|
case StatusField::class: |
91
|
8 |
|
preg_match('/(\d{1}).(\d{1,3}).(\d{1,3})/', $value, $matches); |
92
|
8 |
|
$header = new StatusField($name, (int) $matches[1], (int) $matches[2], (int) $matches[3], $comment); |
93
|
8 |
|
break; |
94
|
|
|
default: |
95
|
2 |
|
$header = new ValueField($name, $value, $comment); |
96
|
|
|
} |
97
|
8 |
|
$blockFields->add($header); |
98
|
|
|
} |
99
|
|
|
|
100
|
8 |
|
return $blockFields; |
101
|
|
|
} |
102
|
|
|
} |
103
|
|
|
|