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