Context.php
4 weeks ago
FileUpload.php
4 weeks ago
Helpers.php
4 weeks ago
IRequest.php
4 weeks ago
IResponse.php
4 weeks ago
Request.php
4 weeks ago
RequestFactory.php
4 weeks ago
Response.php
4 weeks ago
Session.php
4 weeks ago
SessionSection.php
4 weeks ago
Url.php
4 weeks ago
UrlImmutable.php
4 weeks ago
UrlScript.php
4 weeks ago
UserStorage.php
4 weeks ago
RequestFactory.php
272 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * This file is part of the Nette Framework (https://nette.org) |
| 5 | * Copyright (c) 2004 David Grudl (https://davidgrudl.com) |
| 6 | */ |
| 7 | declare (strict_types=1); |
| 8 | namespace FapiMember\Library\Nette\Http; |
| 9 | |
| 10 | use FapiMember\Library\Nette; |
| 11 | use FapiMember\Library\Nette\Utils\Arrays; |
| 12 | use FapiMember\Library\Nette\Utils\Strings; |
| 13 | /** |
| 14 | * HTTP request factory. |
| 15 | */ |
| 16 | class RequestFactory |
| 17 | { |
| 18 | use Nette\SmartObject; |
| 19 | /** @internal */ |
| 20 | private const ValidChars = '\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}'; |
| 21 | /** @var array */ |
| 22 | public $urlFilters = [ |
| 23 | 'path' => ['#//#' => '/'], |
| 24 | // '%20' => '' |
| 25 | 'url' => [], |
| 26 | ]; |
| 27 | /** @var bool */ |
| 28 | private $binary = \false; |
| 29 | /** @var string[] */ |
| 30 | private $proxies = []; |
| 31 | /** @return static */ |
| 32 | public function setBinary(bool $binary = \true) |
| 33 | { |
| 34 | $this->binary = $binary; |
| 35 | return $this; |
| 36 | } |
| 37 | /** |
| 38 | * @param string|string[] $proxy |
| 39 | * @return static |
| 40 | */ |
| 41 | public function setProxy($proxy) |
| 42 | { |
| 43 | $this->proxies = (array) $proxy; |
| 44 | return $this; |
| 45 | } |
| 46 | /** |
| 47 | * Returns new Request instance, using values from superglobals. |
| 48 | */ |
| 49 | public function fromGlobals(): Request |
| 50 | { |
| 51 | $url = new Url(); |
| 52 | $this->getServer($url); |
| 53 | $this->getPathAndQuery($url); |
| 54 | [$post, $cookies] = $this->getGetPostCookie($url); |
| 55 | [$remoteAddr, $remoteHost] = $this->getClient($url); |
| 56 | return new Request(new UrlScript($url, $this->getScriptPath($url)), $post, $this->getFiles(), $cookies, $this->getHeaders(), $this->getMethod(), $remoteAddr, $remoteHost, function (): string { |
| 57 | return file_get_contents('php://input'); |
| 58 | }); |
| 59 | } |
| 60 | private function getServer(Url $url): void |
| 61 | { |
| 62 | $url->setScheme((!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off')) ? 'https' : 'http'); |
| 63 | if ((isset($_SERVER[$tmp = 'HTTP_HOST']) || isset($_SERVER[$tmp = 'SERVER_NAME'])) && $pair = $this->parseHostAndPort($_SERVER[$tmp])) { |
| 64 | $url->setHost($pair[0]); |
| 65 | if (isset($pair[1])) { |
| 66 | $url->setPort($pair[1]); |
| 67 | } elseif ($tmp === 'SERVER_NAME' && isset($_SERVER['SERVER_PORT'])) { |
| 68 | $url->setPort((int) $_SERVER['SERVER_PORT']); |
| 69 | } |
| 70 | } |
| 71 | } |
| 72 | private function getPathAndQuery(Url $url): void |
| 73 | { |
| 74 | $requestUrl = $_SERVER['REQUEST_URI'] ?? '/'; |
| 75 | $requestUrl = preg_replace('#^\w++://[^/]++#', '', $requestUrl); |
| 76 | $requestUrl = Strings::replace($requestUrl, $this->urlFilters['url']); |
| 77 | $tmp = explode('?', $requestUrl, 2); |
| 78 | $path = Url::unescape($tmp[0], '%/?#'); |
| 79 | $path = Strings::fixEncoding(Strings::replace($path, $this->urlFilters['path'])); |
| 80 | $url->setPath($path); |
| 81 | $url->setQuery($tmp[1] ?? ''); |
| 82 | } |
| 83 | private function getScriptPath(Url $url): string |
| 84 | { |
| 85 | if (\PHP_SAPI === 'cli-server') { |
| 86 | return '/'; |
| 87 | } |
| 88 | $path = $url->getPath(); |
| 89 | $lpath = strtolower($path); |
| 90 | $script = strtolower($_SERVER['SCRIPT_NAME'] ?? ''); |
| 91 | if ($lpath !== $script) { |
| 92 | $max = min(strlen($lpath), strlen($script)); |
| 93 | for ($i = 0; $i < $max && $lpath[$i] === $script[$i]; $i++) { |
| 94 | } |
| 95 | $path = $i ? substr($path, 0, strrpos($path, '/', $i - strlen($path) - 1) + 1) : '/'; |
| 96 | } |
| 97 | return $path; |
| 98 | } |
| 99 | private function getGetPostCookie(Url $url): array |
| 100 | { |
| 101 | $useFilter = !in_array((string) ini_get('filter.default'), ['', 'unsafe_raw'], \true) || ini_get('filter.default_flags'); |
| 102 | $query = $url->getQueryParameters(); |
| 103 | $post = $useFilter ? filter_input_array(\INPUT_POST, \FILTER_UNSAFE_RAW) : (empty($_POST) ? [] : $_POST); |
| 104 | $cookies = $useFilter ? filter_input_array(\INPUT_COOKIE, \FILTER_UNSAFE_RAW) : (empty($_COOKIE) ? [] : $_COOKIE); |
| 105 | // remove invalid characters |
| 106 | $reChars = '#^[' . self::ValidChars . ']*+$#Du'; |
| 107 | if (!$this->binary) { |
| 108 | $list = [&$query, &$post, &$cookies]; |
| 109 | foreach ($list as $key => &$val) { |
| 110 | foreach ($val as $k => $v) { |
| 111 | if (is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { |
| 112 | unset($list[$key][$k]); |
| 113 | } elseif (is_array($v)) { |
| 114 | $list[$key][$k] = $v; |
| 115 | $list[] =& $list[$key][$k]; |
| 116 | } elseif (is_string($v)) { |
| 117 | $list[$key][$k] = (string) preg_replace('#[^' . self::ValidChars . ']+#u', '', $v); |
| 118 | } else { |
| 119 | throw new Nette\InvalidStateException(sprintf('Invalid value in $_POST/$_COOKIE in key %s, expected string, %s given.', "'{$k}'", gettype($v))); |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | unset($list, $key, $val, $k, $v); |
| 124 | } |
| 125 | $url->setQuery($query); |
| 126 | return [$post, $cookies]; |
| 127 | } |
| 128 | private function getFiles(): array |
| 129 | { |
| 130 | $reChars = '#^[' . self::ValidChars . ']*+$#Du'; |
| 131 | $files = []; |
| 132 | $list = []; |
| 133 | foreach ($_FILES ?? [] as $k => $v) { |
| 134 | if (!is_array($v) || !isset($v['name'], $v['type'], $v['size'], $v['tmp_name'], $v['error']) || !$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { |
| 135 | continue; |
| 136 | } |
| 137 | $v['@'] =& $files[$k]; |
| 138 | $list[] = $v; |
| 139 | } |
| 140 | // create FileUpload objects |
| 141 | foreach ($list as &$v) { |
| 142 | if (!isset($v['name'])) { |
| 143 | continue; |
| 144 | } elseif (!is_array($v['name'])) { |
| 145 | if (!$this->binary && (!preg_match($reChars, $v['name']) || preg_last_error())) { |
| 146 | $v['name'] = ''; |
| 147 | } |
| 148 | if ($v['error'] !== \UPLOAD_ERR_NO_FILE) { |
| 149 | $v['@'] = new FileUpload($v); |
| 150 | } |
| 151 | continue; |
| 152 | } |
| 153 | foreach ($v['name'] as $k => $foo) { |
| 154 | if (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { |
| 155 | continue; |
| 156 | } |
| 157 | $list[] = ['name' => $v['name'][$k], 'type' => $v['type'][$k], 'size' => $v['size'][$k], 'full_path' => $v['full_path'][$k] ?? null, 'tmp_name' => $v['tmp_name'][$k], 'error' => $v['error'][$k], '@' => &$v['@'][$k]]; |
| 158 | } |
| 159 | } |
| 160 | return $files; |
| 161 | } |
| 162 | private function getHeaders(): array |
| 163 | { |
| 164 | if (function_exists('apache_request_headers')) { |
| 165 | $headers = apache_request_headers(); |
| 166 | } else { |
| 167 | $headers = []; |
| 168 | foreach ($_SERVER as $k => $v) { |
| 169 | if (strncmp($k, 'HTTP_', 5) === 0) { |
| 170 | $k = substr($k, 5); |
| 171 | } elseif (strncmp($k, 'CONTENT_', 8)) { |
| 172 | continue; |
| 173 | } |
| 174 | $headers[strtr($k, '_', '-')] = $v; |
| 175 | } |
| 176 | } |
| 177 | if (!isset($headers['Authorization'])) { |
| 178 | if (isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) { |
| 179 | $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW']); |
| 180 | } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { |
| 181 | $headers['Authorization'] = 'Digest ' . $_SERVER['PHP_AUTH_DIGEST']; |
| 182 | } |
| 183 | } |
| 184 | return $headers; |
| 185 | } |
| 186 | private function getMethod(): string |
| 187 | { |
| 188 | $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; |
| 189 | if ($method === 'POST' && preg_match('#^[A-Z]+$#D', $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? '')) { |
| 190 | $method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; |
| 191 | } |
| 192 | return $method; |
| 193 | } |
| 194 | private function getClient(Url $url): array |
| 195 | { |
| 196 | $remoteAddr = (!empty($_SERVER['REMOTE_ADDR'])) ? trim($_SERVER['REMOTE_ADDR'], '[]') : null; |
| 197 | // use real client address and host if trusted proxy is used |
| 198 | $usingTrustedProxy = $remoteAddr && Arrays::some($this->proxies, function (string $proxy) use ($remoteAddr): bool { |
| 199 | return Helpers::ipMatch($remoteAddr, $proxy); |
| 200 | }); |
| 201 | if ($usingTrustedProxy) { |
| 202 | $remoteHost = null; |
| 203 | $remoteAddr = empty($_SERVER['HTTP_FORWARDED']) ? $this->useNonstandardProxy($url) : $this->useForwardedProxy($url); |
| 204 | } else { |
| 205 | $remoteHost = (!empty($_SERVER['REMOTE_HOST'])) ? $_SERVER['REMOTE_HOST'] : null; |
| 206 | } |
| 207 | return [$remoteAddr, $remoteHost]; |
| 208 | } |
| 209 | private function useForwardedProxy(Url $url): ?string |
| 210 | { |
| 211 | $forwardParams = preg_split('/[,;]/', $_SERVER['HTTP_FORWARDED']); |
| 212 | foreach ($forwardParams as $forwardParam) { |
| 213 | [$key, $value] = explode('=', $forwardParam, 2) + [1 => '']; |
| 214 | $proxyParams[strtolower(trim($key))][] = trim($value, " \t\""); |
| 215 | } |
| 216 | if (isset($proxyParams['for'])) { |
| 217 | $address = $proxyParams['for'][0]; |
| 218 | $remoteAddr = (strpos($address, '[') === \false) ? explode(':', $address)[0] : substr($address, 1, strpos($address, ']') - 1); |
| 219 | // IPv6 |
| 220 | } |
| 221 | if (isset($proxyParams['proto']) && count($proxyParams['proto']) === 1) { |
| 222 | $url->setScheme((strcasecmp($proxyParams['proto'][0], 'https') === 0) ? 'https' : 'http'); |
| 223 | $url->setPort(($url->getScheme() === 'https') ? 443 : 80); |
| 224 | } |
| 225 | if (isset($proxyParams['host']) && count($proxyParams['host']) === 1 && $pair = $this->parseHostAndPort($proxyParams['host'][0])) { |
| 226 | $url->setHost($pair[0]); |
| 227 | if (isset($pair[1])) { |
| 228 | $url->setPort($pair[1]); |
| 229 | } |
| 230 | } |
| 231 | return $remoteAddr ?? null; |
| 232 | } |
| 233 | private function useNonstandardProxy(Url $url): ?string |
| 234 | { |
| 235 | if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { |
| 236 | $url->setScheme((strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0) ? 'https' : 'http'); |
| 237 | $url->setPort(($url->getScheme() === 'https') ? 443 : 80); |
| 238 | } |
| 239 | if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { |
| 240 | $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); |
| 241 | } |
| 242 | if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { |
| 243 | $xForwardedForWithoutProxies = array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), function (string $ip): bool { |
| 244 | return filter_var(trim($ip), \FILTER_VALIDATE_IP) === \false || !Arrays::some($this->proxies, function (string $proxy) use ($ip): bool { |
| 245 | return Helpers::ipMatch(trim($ip), $proxy); |
| 246 | }); |
| 247 | }); |
| 248 | if ($xForwardedForWithoutProxies) { |
| 249 | $remoteAddr = trim(end($xForwardedForWithoutProxies)); |
| 250 | $xForwardedForRealIpKey = key($xForwardedForWithoutProxies); |
| 251 | } |
| 252 | } |
| 253 | if (isset($xForwardedForRealIpKey) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { |
| 254 | $xForwardedHost = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']); |
| 255 | if (isset($xForwardedHost[$xForwardedForRealIpKey])) { |
| 256 | $url->setHost(trim($xForwardedHost[$xForwardedForRealIpKey])); |
| 257 | } |
| 258 | } |
| 259 | return $remoteAddr ?? null; |
| 260 | } |
| 261 | /** @return array{string, ?int}|null */ |
| 262 | private function parseHostAndPort(string $s): ?array |
| 263 | { |
| 264 | return preg_match('#^([a-z0-9_.-]+|\[[a-f0-9:]+])(:\d+)?$#Di', $s, $matches) ? [rtrim(strtolower($matches[1]), '.'), isset($matches[2]) ? (int) substr($matches[2], 1) : null] : null; |
| 265 | } |
| 266 | /** @deprecated */ |
| 267 | public function createHttpRequest(): Request |
| 268 | { |
| 269 | return $this->fromGlobals(); |
| 270 | } |
| 271 | } |
| 272 |