Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 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 a 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
     * @return string A string suitable for use as a Content-Disposition field-value
158
     *
159
     * @throws \InvalidArgumentException
160
     *
161
     * @see RFC 6266
162
     */
163
    public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string
164
    {
165
        if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) {
166
            throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
167
        }
168
 
169
        if ('' === $filenameFallback) {
170
            $filenameFallback = $filename;
171
        }
172
 
173
        // filenameFallback is not ASCII.
174
        if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
175
            throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
176
        }
177
 
178
        // percent characters aren't safe in fallback.
179
        if (false !== strpos($filenameFallback, '%')) {
180
            throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
181
        }
182
 
183
        // path separators aren't allowed in either.
184
        if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) {
185
            throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
186
        }
187
 
188
        $params = ['filename' => $filenameFallback];
189
        if ($filename !== $filenameFallback) {
190
            $params['filename*'] = "utf-8''".rawurlencode($filename);
191
        }
192
 
193
        return $disposition.'; '.self::toString($params, ';');
194
    }
195
 
196
    private static function groupParts(array $matches, string $separators): array
197
    {
198
        $separator = $separators[0];
199
        $partSeparators = substr($separators, 1);
200
 
201
        $i = 0;
202
        $partMatches = [];
203
        foreach ($matches as $match) {
204
            if (isset($match['separator']) && $match['separator'] === $separator) {
205
                ++$i;
206
            } else {
207
                $partMatches[$i][] = $match;
208
            }
209
        }
210
 
211
        $parts = [];
212
        if ($partSeparators) {
213
            foreach ($partMatches as $matches) {
214
                $parts[] = self::groupParts($matches, $partSeparators);
215
            }
216
        } else {
217
            foreach ($partMatches as $matches) {
218
                $parts[] = self::unquote($matches[0][0]);
219
            }
220
        }
221
 
222
        return $parts;
223
    }
224
}