Completed
Pull Request — master (#86)
by Frederik
01:29
created

HeaderV1Factory::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 0
cp 0
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 2
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail\Dkim;
5
6
use Genkgo\Mail\Header\HeaderName;
7
use Genkgo\Mail\Header\HeaderValue;
8
use Genkgo\Mail\Header\HeaderValueParameter;
9
use Genkgo\Mail\HeaderInterface;
10
use Genkgo\Mail\MessageInterface;
11
12
final class HeaderV1Factory
13
{
14
    public const HEADER_NAME = 'DKIM-Signature';
15
16
    /**
17
     * @var array
18
     */
19
    private const HEADERS_IGNORED = [
20
        'return-path' => true,
21
        'received' => true,
22
        'comments' => true,
23
        'keywords' => true,
24
        'bcc' => true,
25
        'resent-bcc' => true,
26
    ];
27
28
    /**
29
     * @var SignInterface
30
     */
31
    private $sign;
32
33
    /**
34
     * @var CanonicalizeHeaderInterface
35
     */
36
    private $canonicalizeHeader;
37
38
    /**
39
     * @var CanonicalizeBodyInterface
40
     */
41
    private $canonicalizeBody;
42
43
    /**
44
     * @param SignInterface $sign
45
     * @param CanonicalizeHeaderInterface $canonicalizeHeader
46
     * @param CanonicalizeBodyInterface $canonicalizeBody
47
     */
48
    public function __construct(
49
        SignInterface $sign,
50
        CanonicalizeHeaderInterface $canonicalizeHeader,
51
        CanonicalizeBodyInterface $canonicalizeBody
52
    ) {
53
        $this->sign = $sign;
54
        $this->canonicalizeHeader = $canonicalizeHeader;
55
        $this->canonicalizeBody = $canonicalizeBody;
56
    }
57
58
    /**
59
     * @param MessageInterface $message
60
     * @param Parameters $parameters
61
     * @return HeaderInterface
62
     */
63
    public function factory(
64
        MessageInterface $message,
65
        Parameters $parameters
66
    ): HeaderInterface {
67
        $headerCanonicalized = [];
68
        $headerNames = [];
69
        foreach ($message->getHeaders() as $headers) {
70
            /** @var HeaderInterface $header */
71
            foreach ($headers as $header) {
72
                $headerName = \strtolower((string)$header->getName());
73
                if (isset(self::HEADERS_IGNORED[$headerName])) {
74
                    break;
75
                }
76
77
                // make sure we sign only the last header in the list
78
                $headerCanonicalized[$headerName] = $this->canonicalizeHeader->canonicalize($header);
79
                $headerNames[$headerName] = (string)$header->getName();
80
            }
81
        }
82
83
        $bodyHash = $this->sign->hashBody(
84
            $this->canonicalizeBody->canonicalize($message->getBody())
85
        );
86
87
        $canonicalization = [$this->canonicalizeHeader->name(), $this->canonicalizeBody->name()];
88
89
        $headerValue = $parameters->newHeaderValue()
90
            ->withParameter($this->newUnquotedParameter('a', $this->sign->name()))
91
            ->withParameter($this->newUnquotedParameter('c', \implode('/', $canonicalization)))
92
            ->withParameter($this->newUnquotedParameter('h', \implode(':', $headerNames)), true)
93
            ->withParameter($this->newUnquotedParameter('bh', \base64_encode($bodyHash)), true)
94
            ->withParameter($this->newUnquotedParameter('b', ''), true);
95
96
        $headerCanonicalized[\strtolower(self::HEADER_NAME)] = $this->canonicalizeHeader->canonicalize(
97
            $this->newHeader($headerValue)
98
        );
99
100
        $headers = \implode("\r\n", $headerCanonicalized);
101
        $signature = \base64_encode($this->sign->signHeaders($headers));
102
        $headerValue = $headerValue->withParameter(new HeaderValueParameter('b', $signature), true);
103
104
        return $this->newHeader($headerValue);
105
    }
106
107
    /**
108
     * @param string $name
109
     * @param string $value
110
     * @return HeaderValueParameter
111
     */
112
    private function newUnquotedParameter(string $name, string $value): HeaderValueParameter
113
    {
114
        return (new HeaderValueParameter($name, $value))->withSpecials('');
115
    }
116
117
    /**
118
     * @param HeaderValue $headerValue
119
     * @return HeaderInterface
120
     */
121 5
    private function newHeader(HeaderValue $headerValue): HeaderInterface
122
    {
123
        return new class($headerValue) implements HeaderInterface {
124
            /**
125
             * @var HeaderValue
126
             */
127
            private $headerValue;
128
129
            /**
130
             * @param HeaderValue $headerValue
131
             */
132
            public function __construct(HeaderValue $headerValue)
133
            {
134 5
                $this->headerValue = $headerValue;
135 5
            }
136
137
            /**
138
             * @return HeaderName
139
             */
140 4
            public function getName(): HeaderName
141
            {
142 4
                return new HeaderName(HeaderV1Factory::HEADER_NAME);
143
            }
144
145
            /**
146
             * @return HeaderValue
147
             */
148 5
            public function getValue(): HeaderValue
149
            {
150 5
                return $this->headerValue;
151
            }
152
        };
153
    }
154
}
155