Passed
Push — master ( 46ed75...ca2387 )
by Zaahid
03:33
created

ParameterConsumerService   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 162
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 23
eloc 55
c 0
b 0
f 0
dl 0
loc 162
ccs 63
cts 63
cp 1
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A addToSplitPart() 0 14 2
A getTokenSplitPattern() 0 5 1
A getTokenSeparators() 0 3 1
A getPartFor() 0 14 4
A getPartForToken() 0 6 2
A finalizeParameterParts() 0 12 3
A processTokenPart() 0 13 4
A processParts() 0 18 6
1
<?php
2
/**
3
 * This file is part of the ZBateson\MailMimeParser project.
4
 *
5
 * @license http://opensource.org/licenses/bsd-license.php BSD
6
 */
7
8
namespace ZBateson\MailMimeParser\Header\Consumer;
9
10
use ArrayObject;
11
use ZBateson\MailMimeParser\Header\IHeaderPart;
12
use ZBateson\MailMimeParser\Header\Part\CommentPart;
13
use ZBateson\MailMimeParser\Header\Part\MimeLiteralPart;
14
use ZBateson\MailMimeParser\Header\Part\SplitParameterToken;
15
use ZBateson\MailMimeParser\Header\Part\Token;
16
17
/**
18
 * Reads headers separated into parameters consisting of an optional main value,
19
 * and subsequent name/value pairs - for example text/html; charset=utf-8.
20
 *
21
 * A ParameterConsumerService's parts are separated by a semi-colon.  Its
22
 * name/value pairs are separated with an '=' character.
23
 *
24
 * Parts may be mime-encoded entities.  Additionally, a value can be quoted and
25
 * comments may exist.
26
 *
27
 * @author Zaahid Bateson
28
 */
29
class ParameterConsumerService extends GenericConsumerService
30
{
31
    /**
32
     * Returns semi-colon and equals char as token separators.
33
     *
34
     * @return string[]
35
     */
36 18
    protected function getTokenSeparators() : array
37
    {
38 18
        return [';', '='];
39
    }
40
41
    /**
42
     * Overridden to use a specialized regex for finding mime-encoded parts
43
     * (RFC 2047).
44
     *
45
     * Some implementations seem to place mime-encoded parts within quoted
46
     * parameters, and split the mime-encoded parts across multiple split
47
     * parameters.  The specialized regex doesn't allow double quotes inside a
48
     * mime encoded part, so it can be "continued" in another parameter.
49
     *
50
     * @return string the regex pattern
51
     */
52 18
    protected function getTokenSplitPattern() : string
53
    {
54 18
        $sChars = \implode('|', $this->getAllTokenSeparators());
55 18
        $mimePartPattern = MimeLiteralPart::MIME_PART_PATTERN_NO_QUOTES;
56 18
        return '~(' . $mimePartPattern . '|\\\\.|' . $sChars . ')~';
57
    }
58
59
    /**
60
     * Creates and returns a \ZBateson\MailMimeParser\Header\Part\Token out of
61
     * the passed string token and returns it, unless the token is an escaped
62
     * literal, in which case a LiteralPart is returned.
63
     */
64 118
    protected function getPartForToken(string $token, bool $isLiteral) : ?IHeaderPart
65
    {
66 118
        if ($isLiteral) {
67 1
            return $this->partFactory->newLiteralPart($token);
68
        }
69 118
        return $this->partFactory->newToken($token);
70
    }
71
72
    /**
73
     * Adds the passed parameter with the given name and value to a
74
     * SplitParameterToken, at the passed index. If one with the given name
75
     * doesn't exist, it is created.
76
     */
77 13
    private function addToSplitPart(
78
        ArrayObject $splitParts,
79
        string $name,
80
        string $value,
81
        int $index,
82
        bool $isEncoded
83
    ) : ?SplitParameterToken {
84 13
        $ret = null;
85 13
        if (!isset($splitParts[$name])) {
86 13
            $ret = $this->partFactory->newSplitParameterToken($name);
87 13
            $splitParts[$name] = $ret;
88
        }
89 13
        $splitParts[$name]->addPart($value, $isEncoded, $index);
90 13
        return $ret;
91
    }
92
93
    /**
94
     * Instantiates and returns either a MimeLiteralPart if $strName is empty,
95
     * a SplitParameterToken if the parameter is a split parameter and is the
96
     * first in a series, null if it's a split parameter but is not the first
97
     * part in its series, or a ParameterPart is returned otherwise.
98
     *
99
     * If the part is a SplitParameterToken, it's added to the passed
100
     * $splitParts as well with its name as a key.
101
     */
102 118
    private function getPartFor(string $strName, string $strValue, ArrayObject $splitParts) : ?IHeaderPart
103
    {
104 118
        if ($strName === '') {
105 118
            return $this->partFactory->newMimeLiteralPart($strValue);
106 117
        } elseif (\preg_match('~^\s*([^\*]+)\*(\d*)(\*)?$~', $strName, $matches)) {
107 13
            return $this->addToSplitPart(
108 13
                $splitParts,
109 13
                $matches[1],
110 13
                $strValue,
111 13
                (int) $matches[2],
112 13
                (($matches[2] === '') || !empty($matches[3]))
113 13
            );
114
        }
115 105
        return $this->partFactory->newParameterPart($strName, $strValue);
116
    }
117
118
    /**
119
     * Handles parameter separator tokens during final processing.
120
     *
121
     * If the end token is found, a new IHeaderPart is assigned to the passed
122
     * $combined array.  If an '=' character is found, $strCat is assigned to
123
     * $strName and emptied.
124
     *
125
     * Returns true if the token was processed, and false otherwise.
126
     *
127
     */
128 118
    private function processTokenPart(string $tokenValue, ArrayObject $combined, ArrayObject $splitParts, string &$strName, string &$strCat) : bool
129
    {
130 118
        if ($tokenValue === ';') {
131 118
            $combined[] = $this->getPartFor($strName, $strCat, $splitParts);
132 118
            $strName = '';
133 118
            $strCat = '';
134 118
            return true;
135 118
        } elseif ($tokenValue === '=' && $strCat !== '') {
136 117
            $strName = $strCat;
137 117
            $strCat = '';
138 117
            return true;
139
        }
140 118
        return false;
141
    }
142
143
    /**
144
     * Loops over parts in the passed array, creating ParameterParts out of any
145
     * parsed SplitParameterTokens, replacing them in the array.
146
     *
147
     * The method then calls filterIgnoreSpaces to filter out empty elements in
148
     * the combined array and returns an array.
149
     *
150
     * @return IHeaderPart[]
151
     */
152 118
    private function finalizeParameterParts(ArrayObject $combined) : array
153
    {
154 118
        foreach ($combined as $key => $part) {
155 118
            if ($part instanceof SplitParameterToken) {
156 13
                $combined[$key] = $this->partFactory->newParameterPart(
157 13
                    $part->getName(),
158 13
                    $part->getValue(),
0 ignored issues
show
Bug introduced by
It seems like $part->getValue() can also be of type null; however, parameter $value of ZBateson\MailMimeParser\...ory::newParameterPart() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

158
                    /** @scrutinizer ignore-type */ $part->getValue(),
Loading history...
159 13
                    $part->getLanguage()
160 13
                );
161
            }
162
        }
163 118
        return $this->filterIgnoredSpaces($combined->getArrayCopy());
164
    }
165
166
    /**
167
     * Post processing involves creating Part\LiteralPart or Part\ParameterPart
168
     * objects out of created Token and LiteralParts.
169
     *
170
     * @param IHeaderPart[] $parts The parsed parts.
171
     * @return IHeaderPart[] Array of resulting final parts.
172
     */
173 118
    protected function processParts(array $parts) : array
174
    {
175 118
        $combined = new ArrayObject();
176 118
        $splitParts = new ArrayObject();
177 118
        $strCat = '';
178 118
        $strName = '';
179 118
        $parts[] = $this->partFactory->newToken(';');
180 118
        foreach ($parts as $part) {
181 118
            $pValue = $part->getValue();
182 118
            if (($part instanceof Token || $part instanceof CommentPart) && $this->processTokenPart($pValue, $combined, $splitParts, $strName, $strCat)) {
0 ignored issues
show
Bug introduced by
It seems like $pValue can also be of type null; however, parameter $tokenValue of ZBateson\MailMimeParser\...ice::processTokenPart() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

182
            if (($part instanceof Token || $part instanceof CommentPart) && $this->processTokenPart(/** @scrutinizer ignore-type */ $pValue, $combined, $splitParts, $strName, $strCat)) {
Loading history...
183 118
                continue;
184 118
            } elseif ($part instanceof CommentPart) {
185 1
                $combined[] = $part;
186
            } else {
187 118
                $strCat .= $pValue;
188
            }
189
        }
190 118
        return $this->finalizeParameterParts($combined);
191
    }
192
}
193