Completed
Pull Request — master (#157)
by Alexander
01:52
created

HeaderHelper::getParameters()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.0092

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 4
nop 1
dl 0
loc 16
ccs 11
cts 12
cp 0.9167
crap 4.0092
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
namespace Yiisoft\Yii\Web\Helper;
4
5
use Psr\Http\Message\RequestInterface;
6
7
final class HeaderHelper
8
{
9
    /**
10
     * Explode header value to value and parameters (eg. text/html;q=2;version=6)
11
     * @param string $headerValue
12
     * @return array first element is the value, and key-value are the parameters
13
     */
14 29
    public static function getValueAndParameters(string $headerValue): array
15
    {
16 29
        $headerValue = trim($headerValue);
17 29
        if ($headerValue === '') {
18 1
            return [];
19
        }
20 28
        $parts = preg_split('/\s*;\s*/', $headerValue, 2, PREG_SPLIT_NO_EMPTY);
21 28
        $output = [$parts[0]];
22 28
        if (count($parts) === 1) {
1 ignored issue
show
Bug introduced by
It seems like $parts can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, 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

22
        if (count(/** @scrutinizer ignore-type */ $parts) === 1) {
Loading history...
23 16
            return $output;
24
        }
25 21
        return array_merge($output, self::getParameters($parts[1]));
26
    }
27
28
    /**
29
     * Explode header value to parameters (eg. q=2;version=6)
30
     */
31 25
    public static function getParameters(string $headerValue): array
32
    {
33 25
        $headerValue = trim($headerValue);
34 25
        if ($headerValue === '') {
35
            return [];
36
        }
37 25
        $parts = preg_split('/\s*;\s*/', $headerValue, -1, PREG_SPLIT_NO_EMPTY);
38 25
        $output = [];
39 25
        foreach ($parts as $part) {
40 25
            [$key, $headerValue] = explode('=', $part, 2);
41 25
            if (preg_match('/^"(?<value>.*)"$/', $headerValue, $matches) === 1) {
42 1
                $headerValue = $matches['value'];
43
            }
44 25
            $output[$key] = $headerValue;
45
        }
46 25
        return $output;
47
    }
48
49
    /**
50
     * Getting header value as q factor sorted list
51
     * @param string|string[] $values Header value as a comma-separated string or already exploded string array.
52
     * @see getValueAndParameters
53
     * @link https://developer.mozilla.org/en-US/docs/Glossary/Quality_values
54
     */
55 32
    public static function getSortedValueAndParameters($values): array
56
    {
57 32
        if (is_string($values)) {
58 21
            $values = preg_split('/\s*,\s*/', trim($values), -1, PREG_SPLIT_NO_EMPTY);
59
        }
60 32
        if (!is_array($values)) {
61 3
            throw new \InvalidArgumentException('Values ​​are neither array nor string');
62
        }
63 29
        if (count($values) === 0) {
64 4
            return [];
65
        }
66 25
        $output = [];
67 25
        foreach ($values as $value) {
68 25
            $parse = self::getValueAndParameters($value);
69 25
            $q = $parse['q'] ?? 1.0;
70
71
            // min 0.000 max 1.000, max 3 digits, without digits allowed
72 25
            if (is_string($q) && preg_match('/^(?:0(?:\.\d{1,3})?|1(?:\.0{1,3})?)$/', $q) === 0) {
73 4
                throw new \InvalidArgumentException('Invalid q factor');
74
            }
75 21
            $parse['q'] = floatval($q);
76 21
            $output[] = $parse;
77
        }
78
        usort($output, static function ($a, $b) {
79 17
            $a = $a['q'];
80 17
            $b = $b['q'];
81 17
            if ($a === $b) {
82 9
                return 0;
83
            }
84 8
            return $a > $b ? -1 : 1;
85 21
        });
86 21
        return $output;
87
    }
88
89
    /**
90
     * @see getSortedAcceptTypes
91
     */
92 6
    public static function getSortedAcceptTypesFromRequest(RequestInterface $request): array
93
    {
94 6
        return static::getSortedAcceptTypes($request->getHeader('accept'));
95
    }
96
97
    /**
98
     * @param $values string|string[] $values Header value as a comma-separated string or already exploded string array
99
     * @return string[] sorted accept types. Note: According to RFC 7231, special parameters (except the q factor) are
100
     *                  added to the type, which are always appended by a semicolon and sorted by string.
101
     * @link https://tools.ietf.org/html/rfc7231#section-5.3.2
102
     */
103 18
    public static function getSortedAcceptTypes($values): array
104
    {
105 18
        $output = self::getSortedValueAndParameters($values);
106
        usort($output, static function ($a, $b) {
107 12
            if ($a['q'] !== $b['q']) {
108
                // The higher q value wins
109 4
                return $a['q'] > $b['q'] ? -1 : 1;
110
            }
111 8
            $typeA = reset($a);
112 8
            $typeB = reset($b);
113 8
            if (strpos($typeA, '*') === false && strpos($typeB, '*') === false) {
114 8
                $countA = count($a);
115 8
                $countB = count($b);
116 8
                if ($countA === $countB) {
117
                    // They are equivalent for the same parameter number
118 5
                    return 0;
119
                }
120
                // No wildcard character, higher parameter number wins
121 3
                return $countA > $countB ? -1 : 1;
122
            }
123 1
            $endWildcardA = substr($typeA, -1, 1) === '*';
124 1
            $endWildcardB = substr($typeB, -1, 1) === '*';
125 1
            if (($endWildcardA && !$endWildcardB) || (!$endWildcardA && $endWildcardB)) {
126
                // The wildcard ends is the loser.
127 1
                return $endWildcardA ? 1 : -1;
128
            }
129
            // The wildcard starts is the loser.
130 1
            return strpos($typeA, '*') === 0 ? 1 : -1;
131 18
        });
132 18
        foreach ($output as $key => $value) {
133 16
            $type = array_shift($value);
134 16
            unset($value['q']);
135 16
            if (count($value) === 0) {
136 15
                $output[$key] = $type;
137 15
                continue;
138
            }
139 5
            foreach ($value as $k => $v) {
140 5
                $value[$k] = $k . '=' . $v;
141
            }
142
            // Parameters are sorted for easier use of parameter variations.
143 5
            asort($value, SORT_STRING);
144 5
            $output[$key] = $type . ';' . join(';', $value);
145
        }
146 18
        return $output;
147
    }
148
}
149