Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Symfony\Component\HttpFoundation;
13
 
14
/**
15
 * HTTP header utility functions.
16
 *
17
 * @author Christian Schmidt <github@chsc.dk>
18
 */
19
class HeaderUtils
20
{
21
    public const DISPOSITION_ATTACHMENT = 'attachment';
22
    public const DISPOSITION_INLINE = 'inline';
23
 
24
    /**
25
     * This class should not be instantiated.
26
     */
27
    private function __construct()
28
    {
29
    }
30
 
31
    /**
32
     * Splits an HTTP header by one or more separators.
33
     *
34
     * Example:
35
     *
36
     *     HeaderUtils::split("da, en-gb;q=0.8", ",;")
37
     *     // => ['da'], ['en-gb', 'q=0.8']]
38
     *
39
     * @param string $separators List of characters to split on, ordered by
40
     *                           precedence, e.g. ",", ";=", or ",;="
41
     *
42
     * @return array Nested array with as many levels as there are characters in
43
     *               $separators
44
     */
45
    public static function split(string $header, string $separators): array
46
    {
47
        $quotedSeparators = preg_quote($separators, '/');
48
 
49
        preg_match_all('
50
            /
51
                (?!\s)
52
                    (?:
53
                        # quoted-string
54
                        "(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$)
55
                    |
56
                        # token
57
                        [^"'.$quotedSeparators.']+
58
                    )+
59
                (?<!\s)
60
            |
61
                # separator
62
                \s*
63
                (?<separator>['.$quotedSeparators.'])
64
                \s*
65
            /x', trim($header), $matches, \PREG_SET_ORDER);
66
 
67
        return self::groupParts($matches, $separators);
68
    }
69
 
70
    /**
71
     * Combines an array of arrays into one associative array.
72
     *
73
     * Each of the nested arrays should have one or two elements. The first
74
     * value will be used as the keys in the associative array, and the second
75
     * will be used as the values, or true if the nested array only contains one
76
     * element. Array keys are lowercased.
77
     *
78
     * Example:
79
     *
80
     *     HeaderUtils::combine([["foo", "abc"], ["bar"]])
81
     *     // => ["foo" => "abc", "bar" => true]
82
     */
83
    public static function combine(array $parts): array
84
    {
85
        $assoc = [];
86
        foreach ($parts as $part) {
87
            $name = strtolower($part[0]);
88
            $value = $part[1] ?? true;
89
            $assoc[$name] = $value;
90
        }
91
 
92
        return $assoc;
93
    }
94
 
95
    /**
96
     * Joins an associative array into a string for use in an HTTP header.
97
     *
98
     * The key and value of each entry are joined with "=", and all entries
99
     * are joined with the specified separator and an additional space (for
100
     * readability). Values are quoted if necessary.
101
     *
102
     * Example:
103
     *
104
     *     HeaderUtils::toString(["foo" => "abc", "bar" => true, "baz" => "a b c"], ",")
105
     *     // => 'foo=abc, bar, baz="a b c"'
106
     */
107
    public static function toString(array $assoc, string $separator): string
108
    {
109
        $parts = [];
110
        foreach ($assoc as $name => $value) {
111
            if (true === $value) {
112
                $parts[] = $name;
113
            } else {
114
                $parts[] = $name.'='.self::quote($value);
115
            }
116
        }
117
 
118
        return implode($separator.' ', $parts);
119
    }
120
 
121
    /**
122
     * Encodes a string as a quoted string, if necessary.
123
     *
124
     * If a string contains characters not allowed by the "token" construct in
125
     * the HTTP specification, it is backslash-escaped and enclosed in quotes
126
     * to match the "quoted-string" construct.
127
     */
128
    public static function quote(string $s): string
129
    {
130
        if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) {
131
            return $s;
132
        }
133
 
134
        return '"'.addcslashes($s, '"\\"').'"';
135
    }
136
 
137
    /**
138
     * Decodes a quoted string.
139
     *
140
     * If passed an unquoted string that matches the "token" construct (as
141
     * defined in the HTTP specification), it is passed through verbatimly.
142
     */
143
    public static function unquote(string $s): string
144
    {
145
        return preg_replace('/\\\\(.)|"/', '$1', $s);
146
    }
147
 
148
    /**
149
     * Generates an HTTP Content-Disposition field-value.
150
     *
151
     * @param string $disposition      One of "inline" or "attachment"
152
     * @param string $filename         A unicode string
153
     * @param string $filenameFallback A string containing only ASCII characters that
154
     *                                 is semantically equivalent to $filename. If the filename is already ASCII,
155
     *                                 it can be omitted, or just copied from $filename
156
     *
157
     * @throws \InvalidArgumentException
158
     *
159
     * @see RFC 6266
160
     */
161
    public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string
162
    {
163
        if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) {
164
            throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
165
        }
166
 
167
        if ('' === $filenameFallback) {
168
            $filenameFallback = $filename;
169
        }
170
 
171
        // filenameFallback is not ASCII.
172
        if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
173
            throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
174
        }
175
 
176
        // percent characters aren't safe in fallback.
177
        if (str_contains($filenameFallback, '%')) {
178
            throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
179
        }
180
 
181
        // path separators aren't allowed in either.
182
        if (str_contains($filename, '/') || str_contains($filename, '\\') || str_contains($filenameFallback, '/') || str_contains($filenameFallback, '\\')) {
183
            throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
184
        }
185
 
186
        $params = ['filename' => $filenameFallback];
187
        if ($filename !== $filenameFallback) {
188
            $params['filename*'] = "utf-8''".rawurlencode($filename);
189
        }
190
 
191
        return $disposition.'; '.self::toString($params, ';');
192
    }
193
 
194
    /**
195
     * Like parse_str(), but preserves dots in variable names.
196
     */
197
    public static function parseQuery(string $query, bool $ignoreBrackets = false, string $separator = '&'): array
198
    {
199
        $q = [];
200
 
201
        foreach (explode($separator, $query) as $v) {
202
            if (false !== $i = strpos($v, "\0")) {
203
                $v = substr($v, 0, $i);
204
            }
205
 
206
            if (false === $i = strpos($v, '=')) {
207
                $k = urldecode($v);
208
                $v = '';
209
            } else {
210
                $k = urldecode(substr($v, 0, $i));
211
                $v = substr($v, $i);
212
            }
213
 
214
            if (false !== $i = strpos($k, "\0")) {
215
                $k = substr($k, 0, $i);
216
            }
217
 
218
            $k = ltrim($k, ' ');
219
 
220
            if ($ignoreBrackets) {
221
                $q[$k][] = urldecode(substr($v, 1));
222
 
223
                continue;
224
            }
225
 
226
            if (false === $i = strpos($k, '[')) {
227
                $q[] = bin2hex($k).$v;
228
            } else {
229
                $q[] = bin2hex(substr($k, 0, $i)).rawurlencode(substr($k, $i)).$v;
230
            }
231
        }
232
 
233
        if ($ignoreBrackets) {
234
            return $q;
235
        }
236
 
237
        parse_str(implode('&', $q), $q);
238
 
239
        $query = [];
240
 
241
        foreach ($q as $k => $v) {
242
            if (false !== $i = strpos($k, '_')) {
243
                $query[substr_replace($k, hex2bin(substr($k, 0, $i)).'[', 0, 1 + $i)] = $v;
244
            } else {
245
                $query[hex2bin($k)] = $v;
246
            }
247
        }
248
 
249
        return $query;
250
    }
251
 
252
    private static function groupParts(array $matches, string $separators, bool $first = true): array
253
    {
254
        $separator = $separators[0];
255
        $partSeparators = substr($separators, 1);
256
 
257
        $i = 0;
258
        $partMatches = [];
259
        $previousMatchWasSeparator = false;
260
        foreach ($matches as $match) {
261
            if (!$first && $previousMatchWasSeparator && isset($match['separator']) && $match['separator'] === $separator) {
262
                $previousMatchWasSeparator = true;
263
                $partMatches[$i][] = $match;
264
            } elseif (isset($match['separator']) && $match['separator'] === $separator) {
265
                $previousMatchWasSeparator = true;
266
                ++$i;
267
            } else {
268
                $previousMatchWasSeparator = false;
269
                $partMatches[$i][] = $match;
270
            }
271
        }
272
 
273
        $parts = [];
274
        if ($partSeparators) {
275
            foreach ($partMatches as $matches) {
276
                $parts[] = self::groupParts($matches, $partSeparators, false);
277
            }
278
        } else {
279
            foreach ($partMatches as $matches) {
280
                $parts[] = self::unquote($matches[0][0]);
281
            }
282
 
283
            if (!$first && 2 < \count($parts)) {
284
                $parts = [
285
                    $parts[0],
286
                    implode($separator, \array_slice($parts, 1)),
287
                ];
288
            }
289
        }
290
 
291
        return $parts;
292
    }
293
}