Completed
Push — master ( 40e61a...4da634 )
by Frederik
02:29
created

HeaderValue::__toString()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 9
cts 9
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 3
nop 0
crap 4
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail\Header;
5
6
use Genkgo\Mail\Stream\OptimalTransferEncodedTextStream;
7
8
/**
9
 * Class HeaderValue
10
 * @package Genkgo\Mail\Header
11
 */
12
final class HeaderValue
13
{
14
    /**
15
     *
16
     */
17
    private const FOLDING = "\r\n ";
18
    /**
19
     * @var string
20
     */
21
    private $value;
22
    /**
23
     * @var array
24
     */
25
    private $parameters = [];
26
27
    /**
28
     * HeaderValue constructor.
29
     * @param string $value
30
     */
31 72
    public function __construct(string $value)
32
    {
33 72
        $this->assertValid($value);
34
35 69
        $this->value = $value;
36 69
    }
37
38
    /**
39
     * @param HeaderValueParameter $parameter
40
     * @return HeaderValue
41
     */
42 23
    public function withParameter(HeaderValueParameter $parameter): HeaderValue
43
    {
44 23
        $clone = clone $this;
45 23
        $clone->parameters[$parameter->getName()] = $parameter;
46 23
        return $clone;
47
    }
48
49
    /**
50
     * @return string
51
     */
52 2
    public function getRaw(): string
53
    {
54 2
        return $this->value;
55
    }
56
57
    /**
58
     * @return string
59
     */
60 59
    public function __toString(): string
61
    {
62 59
        $value = implode('; ', array_merge([$this->value], $this->parameters));
63
64 59
        $encoded = new OptimalTransferEncodedTextStream($value, 68, self::FOLDING);
65 59
        $encoding = $encoded->getMetadata(['transfer-encoding'])['transfer-encoding'];
66 59
        if ($encoding === '7bit' || $encoding === '8bit') {
67 57
            return (string) $encoded;
68
        }
69
70 3
        if ($encoding === 'base64') {
71 2
            return sprintf('=?%s?B?%s?=', 'UTF-8', (string) $encoded);
72
        }
73
74 1
        return sprintf('=?%s?Q?%s?=', 'UTF-8', (string) $encoded);
75
    }
76
77
    /**
78
     * @param string $value
79
     */
80 72
    private function assertValid(string $value): void
81
    {
82 72
        if ($this->validate($value) === false) {
83 3
            throw new \InvalidArgumentException('Invalid header value ' . $value);
84
        }
85 69
    }
86
87
    /**
88
     * @param $value
89
     * @return bool
90
     */
91 72
    private function validate(string $value): bool
92
    {
93 72
        $total = strlen($value);
94
95 72
        for ($i = 0; $i < $total; $i += 1) {
96 72
            $ord = ord($value[$i]);
97 72
            if ($ord === 10) {
98 1
                return false;
99
            }
100 72
            if ($ord === 13) {
101 2
                if ($i + 2 >= $total) {
102
                    return false;
103
                }
104 2
                $lf = ord($value[$i + 1]);
105 2
                $sp = ord($value[$i + 2]);
106 2
                if ($lf !== 10 || ! in_array($sp, [9, 32], true)) {
107 2
                    return false;
108
                }
109
                // skip over the LF following this
110
                $i += 2;
111
            }
112
        }
113
114 69
        return true;
115
    }
116
117
    /**
118
     * @param string $name
119
     * @return HeaderValueParameter
120
     */
121 4
    public function getParameter(string $name): HeaderValueParameter
122
    {
123 4
        if (isset($this->parameters[$name])) {
124 4
            return $this->parameters[$name];
125
        }
126
127 2
        throw new \UnexpectedValueException('No parameter with name ' . $name);
128
    }
129
}