annotate vendor/zendframework/zend-diactoros/src/HeaderSecurity.php @ 19:fa3358dc1485 tip

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