PluginProbe
Packeta / 2.1
Packeta v2.1
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / deps / nette / http / src / Http / RequestFactory.php

RequestFactory.php in Packeta 2.1, at deps/nette/http/src/Http/RequestFactory.php

272 lines 11.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 Packetery\Nette\Http;
9
10 use Packetery\Nette;
11 use Packetery\Nette\Utils\Arrays;
12 use Packetery\Nette\Utils\Strings;
13 /**
14 * HTTP request factory.
15 */
16 class RequestFactory
17 {
18 use \Packetery\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 \Packetery\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