Completed
Pull Request — master (#18)
by Frederik
02:18
created

HeaderV1Factory::factory()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 44
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 44
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 28
nc 3
nop 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
    /**
15
     *
16
     */
17
    public CONST HEADER_NAME = 'DKIM-Signature';
18
    /**
19
     * @var array
20
     */
21
    private CONST HEADERS_IGNORED = [
22
        'return-path' => true,
23
        'received' => true,
24
        'comments' => true,
25
        'keywords' => true,
26
        'bcc' => true,
27
        'resent-bcc' => true,
28
    ];
29
    /**
30
     * @var SignInterface
31
     */
32
    private $sign;
33
    /**
34
     * @var CanonicalizeHeaderInterface
35
     */
36
    private $canonicalizeHeader;
37
    /**
38
     * @var CanonicalizeBodyInterface
39
     */
40
    private $canonicalizeBody;
41
42
    /**
43
     * SignedTransport constructor.
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
    {
68
        $headerCanonicalized = [];
69
        $headerNames = [];
70
        foreach ($message->getHeaders() as $headers) {
71
            /** @var HeaderInterface $header */
72
            foreach ($headers as $header) {
73
                $headerName = strtolower((string)$header->getName());
74
                if (isset(self::HEADERS_IGNORED[$headerName])) {
75
                    break;
76
                }
77
78
                // make sure we sign only the last header in the list
79
                $headerCanonicalized[$headerName] = $this->canonicalizeHeader->canonicalize($header);
80
                $headerNames[$headerName] = (string)$header->getName();
81
            }
82
        }
83
84
        $bodyHash = $this->sign->hashBody(
85
            $this->canonicalizeBody->canonicalize($message->getBody())
86
        );
87
88
        $canonicalization = [$this->canonicalizeHeader->name(), $this->canonicalizeBody->name()];
89
90
        $headerValue = $parameters->newHeaderValue()
91
            ->withParameter($this->newUnquotedParameter('a', $this->sign->name()))
92
            ->withParameter($this->newUnquotedParameter('c', implode('/', $canonicalization)))
93
            ->withParameter($this->newUnquotedParameter('h', implode(':', $headerNames)), true)
94
            ->withParameter($this->newUnquotedParameter('bh', base64_encode($bodyHash)), true)
95
            ->withParameter($this->newUnquotedParameter('b', ''), true);
96
97
        $headerCanonicalized[strtolower(self::HEADER_NAME)] = $this->canonicalizeHeader->canonicalize(
98
            $this->newHeader($headerValue)
99
        );
100
101
        $headers = implode("\r\n", $headerCanonicalized);
102
        $signature = base64_encode($this->sign->signHeaders($headers));
103
        $headerValue = $headerValue->withParameter(new HeaderValueParameter('b', $signature), true);
104
105
        return $this->newHeader($headerValue);
106
    }
107
108
    /**
109
     * @param string $name
110
     * @param string $value
111
     * @return HeaderValueParameter
112
     */
113
    private function newUnquotedParameter(string $name, string $value): HeaderValueParameter
114
    {
115
        return (new HeaderValueParameter($name, $value))->withSpecials('');
116
    }
117
118
    /**
119
     * @param HeaderValue $headerValue
120
     * @return HeaderInterface
121
     */
122
    private function newHeader(HeaderValue $headerValue): HeaderInterface
123
    {
124
        return new class ($headerValue) implements HeaderInterface
125
        {
126
            /**
127
             * @var HeaderValue
128
             */
129
            private $headerValue;
130
131
            /**
132
             * DkimSignature constructor.
133
             * @param HeaderValue $headerValue
134
             */
135
            public function __construct(HeaderValue $headerValue)
136
            {
137
                $this->headerValue = $headerValue;
138
            }
139
140
            /**
141
             * @return HeaderName
142
             */
143
            public function getName(): HeaderName
144
            {
145
                return new HeaderName(HeaderV1Factory::HEADER_NAME);
146
            }
147
148
            /**
149
             * @return HeaderValue
150
             */
151
            public function getValue(): HeaderValue
152
            {
153
                return $this->headerValue;
154
            }
155
        };
156
    }
157
158
}