ExtendedParameter   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 46
c 1
b 0
f 0
dl 0
loc 176
ccs 57
cts 57
cp 1
rs 10
wmc 24

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A fromString() 0 11 3
A setValue() 0 9 2
A getCharset() 0 3 1
A setCharset() 0 19 5
A decodeValue() 0 7 2
A getLanguage() 0 3 1
A __toString() 0 4 1
A setLanguage() 0 9 3
A setName() 0 9 3
A encodeValue() 0 3 1
A canEncodeValue() 0 4 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stadly\Http\Header\Value\ContentDisposition;
6
7
use InvalidArgumentException;
8
use Stadly\Http\Utilities\Rfc5987;
9
use Stadly\Http\Utilities\Rfc6266;
10
11
/**
12
 * Class for handling extended content disposition parameters.
13
 *
14
 * Specification: https://tools.ietf.org/html/rfc6266#section-4.1
15
 */
16
final class ExtendedParameter extends Parameter
17
{
18
    /**
19
     * @var string Character set.
20
     */
21
    private $charset;
22
23
    /**
24
     * @var string Language.
25
     */
26
    private $language;
27
28
    /**
29
     * Constructor.
30
     *
31
     * @param string $name Name. Usually `filename*`.
32
     * @param string $value Value.
33
     * @param string $charset Character set.
34
     * @param string $language Language.
35
     */
36 12
    public function __construct(string $name, string $value, string $charset = 'UTF-8', string $language = '')
37
    {
38
        // Value must be a string before setting the character set.
39 12
        $this->value = '';
40
41 12
        $this->setName($name);
42 10
        $this->setCharset($charset);
43 7
        $this->setValue($value);
44 5
        $this->setLanguage($language);
45
    }
46
47
    /**
48
     * Construct parameter from string.
49
     *
50
     * @param string $parameter Parameter string.
51
     * @return self Parameter generated based on the string.
52
     */
53 13
    public static function fromString(string $parameter): self
54
    {
55 13
        $regEx = '{^' . Rfc6266::DISPOSITION_PARM_EXTENDED_CAPTURE . '$}';
56 13
        $plainParameter = mb_convert_encoding($parameter, 'ISO-8859-1', 'UTF-8');
57 13
        if ($plainParameter !== $parameter || preg_match($regEx, $parameter, $matches) !== 1) {
58 8
            throw new InvalidArgumentException('Invalid parameter: ' . $parameter);
59
        }
60
61 5
        $value = self::decodeValue($matches['VALUE_CHARS'], $matches['CHARSET']);
62
63 4
        return new self($matches['NAME'], $value, $matches['CHARSET'], $matches['LANGUAGE']);
64
    }
65
66
    /**
67
     * @inheritDoc
68
     */
69 2
    public function __toString(): string
70
    {
71 2
        $value = self::encodeValue($this->value, $this->charset);
72 2
        return $this->name . '=' . $this->charset . "'" . $this->language . "'" . $value;
73
    }
74
75
    /**
76
     * @inheritDoc
77
     */
78 3
    public function setName(string $name): void
79
    {
80
        // Name must end with *.
81 3
        $plainName = mb_convert_encoding($name, 'ISO-8859-1', 'UTF-8');
82 3
        if ($plainName !== $name || preg_match('{^' . Rfc6266::EXT_TOKEN . '$}', $name) !== 1) {
83 2
            throw new InvalidArgumentException('Invalid name: ' . $name);
84
        }
85
86 3
        $this->name = $name;
87
    }
88
89
    /**
90
     * @inheritDoc
91
     */
92 3
    public function setValue(string $value): void
93
    {
94 3
        if (!self::canEncodeValue($value, $this->charset)) {
95 1
            throw new InvalidArgumentException(
96 1
                'Value incompatible with character set ' . $this->charset . ': ' . $value
97 1
            );
98
        }
99
100 3
        $this->value = $value;
101
    }
102
103
    /**
104
     * @return string Character set.
105
     */
106 1
    public function getCharset(): string
107
    {
108 1
        return $this->charset;
109
    }
110
111
    /**
112
     * @param string $charset Character set.
113
     */
114 5
    public function setCharset(string $charset = 'UTF-8'): void
115
    {
116 5
        $regEx = '{^' . Rfc5987::CHARSET . '$}';
117 5
        $plainCharset = mb_convert_encoding($charset, 'ISO-8859-1', 'UTF-8');
118 5
        if ($plainCharset !== $charset || preg_match($regEx, $charset) !== 1) {
119 2
            throw new InvalidArgumentException('Invalid character set: ' . $charset);
120
        }
121
122 5
        if (!in_array(strtolower($charset), array_map('strtolower', mb_list_encodings()), /*strict*/true)) {
123 1
            throw new InvalidArgumentException('Unsupported character set' . $charset);
124
        }
125
126 5
        if (!self::canEncodeValue($this->value, $charset)) {
127 1
            throw new InvalidArgumentException(
128 1
                'Value incompatible with character set ' . $charset . ': ' . $this->value
129 1
            );
130
        }
131
132 5
        $this->charset = $charset;
133
    }
134
135
    /**
136
     * @return string Language.
137
     */
138 1
    public function getLanguage(): string
139
    {
140 1
        return $this->language;
141
    }
142
143
    /**
144
     * @param string $language Language.
145
     */
146 3
    public function setLanguage(string $language): void
147
    {
148
        // Language can be empty.
149 3
        $plainLanguage = mb_convert_encoding($language, 'ISO-8859-1', 'UTF-8');
150 3
        if ($plainLanguage !== $language || preg_match('{^' . Rfc5987::LANGUAGE . '?$}', $language) !== 1) {
151 1
            throw new InvalidArgumentException('Invalid language: ' . $language);
152
        }
153
154 3
        $this->language = $language;
155
    }
156
157
    /**
158
     * Check if a value can be encoded using a character set.
159
     *
160
     * @param string $value Value to encode.
161
     * @param string $charset Character set.
162
     * @return bool Whether the value can be encoded using the character set.
163
     */
164 31
    private static function canEncodeValue(string $value, string $charset): bool
165
    {
166 31
        $convertedValue = mb_convert_encoding($value, $charset);
167 31
        return mb_convert_encoding($convertedValue, mb_internal_encoding(), $charset) === $value;
0 ignored issues
show
Bug introduced by
It seems like mb_internal_encoding() can also be of type true; however, parameter $to_encoding of mb_convert_encoding() 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

167
        return mb_convert_encoding($convertedValue, /** @scrutinizer ignore-type */ mb_internal_encoding(), $charset) === $value;
Loading history...
168
    }
169
170
    /**
171
     * @param string $value Value.
172
     * @param string $charset Character set.
173
     * @return string Encoded value.
174
     */
175 2
    private static function encodeValue(string $value, string $charset): string
176
    {
177 2
        return rawurlencode(mb_convert_encoding($value, $charset));
0 ignored issues
show
Bug introduced by
It seems like mb_convert_encoding($value, $charset) can also be of type array; however, parameter $string of rawurlencode() 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

177
        return rawurlencode(/** @scrutinizer ignore-type */ mb_convert_encoding($value, $charset));
Loading history...
178
    }
179
180
    /**
181
     * @param string $value Value.
182
     * @param string $charset Character set.
183
     * @return string Decoded value.
184
     */
185 5
    private static function decodeValue(string $value, string $charset): string
186
    {
187 5
        if (!in_array(strtolower($charset), array_map('strtolower', mb_list_encodings()), /*strict*/true)) {
188 1
            throw new InvalidArgumentException('Unsupported character set' . $charset);
189
        }
190
191 4
        return mb_convert_encoding(rawurldecode($value), mb_internal_encoding(), $charset);
0 ignored issues
show
Bug introduced by
It seems like mb_internal_encoding() can also be of type true; however, parameter $to_encoding of mb_convert_encoding() 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

191
        return mb_convert_encoding(rawurldecode($value), /** @scrutinizer ignore-type */ mb_internal_encoding(), $charset);
Loading history...
Bug Best Practice introduced by
The expression return mb_convert_encodi...l_encoding(), $charset) could return the type array which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
192
    }
193
}
194