929db97dcb418e7e7e18234d6b09e34e99a33c38
[yaffs-website] / vendor / zendframework / zend-diactoros / src / HeaderSecurity.php
1 <?php
2 /**
3  * @see       https://github.com/zendframework/zend-diactoros for the canonical source repository
4  * @copyright Copyright (c) 2015-2017 Zend Technologies USA Inc. (http://www.zend.com)
5  * @license   https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
6  */
7
8 namespace Zend\Diactoros;
9
10 use InvalidArgumentException;
11
12 /**
13  * Provide security tools around HTTP headers to prevent common injection vectors.
14  *
15  * Code is largely lifted from the Zend\Http\Header\HeaderValue implementation in
16  * Zend Framework, released with the copyright and license below.
17  *
18  * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
19  * @license   http://framework.zend.com/license/new-bsd New BSD License
20  */
21 final class HeaderSecurity
22 {
23     /**
24      * Private constructor; non-instantiable.
25      * @codeCoverageIgnore
26      */
27     private function __construct()
28     {
29     }
30
31     /**
32      * Filter a header value
33      *
34      * Ensures CRLF header injection vectors are filtered.
35      *
36      * Per RFC 7230, only VISIBLE ASCII characters, spaces, and horizontal
37      * tabs are allowed in values; header continuations MUST consist of
38      * a single CRLF sequence followed by a space or horizontal tab.
39      *
40      * This method filters any values not allowed from the string, and is
41      * lossy.
42      *
43      * @see http://en.wikipedia.org/wiki/HTTP_response_splitting
44      * @param string $value
45      * @return string
46      */
47     public static function filter($value)
48     {
49         $value  = (string) $value;
50         $length = strlen($value);
51         $string = '';
52         for ($i = 0; $i < $length; $i += 1) {
53             $ascii = ord($value[$i]);
54
55             // Detect continuation sequences
56             if ($ascii === 13) {
57                 $lf = ord($value[$i + 1]);
58                 $ws = ord($value[$i + 2]);
59                 if ($lf === 10 && in_array($ws, [9, 32], true)) {
60                     $string .= $value[$i] . $value[$i + 1];
61                     $i += 1;
62                 }
63
64                 continue;
65             }
66
67             // Non-visible, non-whitespace characters
68             // 9 === horizontal tab
69             // 32-126, 128-254 === visible
70             // 127 === DEL
71             // 255 === null byte
72             if (($ascii < 32 && $ascii !== 9)
73                 || $ascii === 127
74                 || $ascii > 254
75             ) {
76                 continue;
77             }
78
79             $string .= $value[$i];
80         }
81
82         return $string;
83     }
84
85     /**
86      * Validate a header value.
87      *
88      * Per RFC 7230, only VISIBLE ASCII characters, spaces, and horizontal
89      * tabs are allowed in values; header continuations MUST consist of
90      * a single CRLF sequence followed by a space or horizontal tab.
91      *
92      * @see http://en.wikipedia.org/wiki/HTTP_response_splitting
93      * @param string $value
94      * @return bool
95      */
96     public static function isValid($value)
97     {
98         $value  = (string) $value;
99
100         // Look for:
101         // \n not preceded by \r, OR
102         // \r not followed by \n, OR
103         // \r\n not followed by space or horizontal tab; these are all CRLF attacks
104         if (preg_match("#(?:(?:(?<!\r)\n)|(?:\r(?!\n))|(?:\r\n(?![ \t])))#", $value)) {
105             return false;
106         }
107
108         // Non-visible, non-whitespace characters
109         // 9 === horizontal tab
110         // 10 === line feed
111         // 13 === carriage return
112         // 32-126, 128-254 === visible
113         // 127 === DEL (disallowed)
114         // 255 === null byte (disallowed)
115         if (preg_match('/[^\x09\x0a\x0d\x20-\x7E\x80-\xFE]/', $value)) {
116             return false;
117         }
118
119         return true;
120     }
121
122     /**
123      * Assert a header value is valid.
124      *
125      * @param string $value
126      * @throws InvalidArgumentException for invalid values
127      */
128     public static function assertValid($value)
129     {
130         if (! is_string($value) && ! is_numeric($value)) {
131             throw new InvalidArgumentException(sprintf(
132                 'Invalid header value type; must be a string or numeric; received %s',
133                 (is_object($value) ? get_class($value) : gettype($value))
134             ));
135         }
136         if (! self::isValid($value)) {
137             throw new InvalidArgumentException(sprintf(
138                 '"%s" is not valid header value',
139                 $value
140             ));
141         }
142     }
143
144     /**
145      * Assert whether or not a header name is valid.
146      *
147      * @see http://tools.ietf.org/html/rfc7230#section-3.2
148      * @param mixed $name
149      * @throws InvalidArgumentException
150      */
151     public static function assertValidName($name)
152     {
153         if (! is_string($name)) {
154             throw new InvalidArgumentException(sprintf(
155                 'Invalid header name type; expected string; received %s',
156                 (is_object($name) ? get_class($name) : gettype($name))
157             ));
158         }
159         if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $name)) {
160             throw new InvalidArgumentException(sprintf(
161                 '"%s" is not valid header name',
162                 $name
163             ));
164         }
165     }
166 }