Elgg  Version master
Request.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Http;
4 
5 use Elgg\Config;
6 use Elgg\Context;
9 use Symfony\Component\HttpFoundation\File\UploadedFile;
10 use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
11 
17 class Request extends SymfonyRequest {
18 
19  const REWRITE_TEST_TOKEN = '__testing_rewrite';
20  const REWRITE_TEST_OUTPUT = 'success';
21 
26  public bool $_integration_testing = false;
27 
31  protected $context_stack;
32 
36  protected $route;
37 
41  protected $request_overrides;
42 
46  protected $filtered_params;
47 
51  protected $unfiltered_params;
52 
56  public function __construct(
57  array $query = [],
58  array $request = [],
59  array $attributes = [],
60  array $cookies = [],
61  array $files = [],
62  array $server = [],
63  $content = null
64  ) {
65  parent::__construct($query, $request, $attributes, $cookies, $files, $server, $content);
66 
67  $this->initializeContext();
68 
69  $this->request_overrides = [];
70  }
71 
80  $trusted_proxies = $config->http_request_trusted_proxy_ips;
81  if (empty($trusted_proxies)) {
82  return;
83  }
84 
85  $allowed_headers = $config->http_request_trusted_proxy_headers;
86  if (empty($allowed_headers)) {
87  $allowed_headers = self::HEADER_X_FORWARDED_FOR | self::HEADER_X_FORWARDED_HOST | self::HEADER_X_FORWARDED_PORT | self::HEADER_X_FORWARDED_PROTO;
88  }
89 
90  $this->setTrustedProxies($trusted_proxies, $allowed_headers);
91  }
92 
98  public function initializeContext() {
99  $context = new Context($this);
100  $this->context_stack = $context;
101 
102  return $this;
103  }
104 
110  public function getContextStack() {
111  return $this->context_stack;
112  }
113 
121  public function setRoute(Route $route) {
122  $this->route = $route;
123  foreach ($route->getMatchedParameters() as $key => $value) {
124  $this->setParam($key, $value);
125  }
126 
127  return $this;
128  }
129 
135  public function getRoute(): ?Route {
136  return $this->route;
137  }
138 
150  public function setParam(string $key, $value, bool $override_request = false) {
151  if ($override_request) {
152  $this->request_overrides[$key] = $value;
153  } else {
154  $this->request->set($key, $value);
155  }
156 
157  // reset the cached params
158  unset($this->filtered_params);
159  unset($this->unfiltered_params);
160 
161  return $this;
162  }
163 
177  public function getParam(string $key, $default = null, bool $filter_result = true) {
178  $values = $this->getParams($filter_result);
179 
180  return elgg_extract($key, $values, $default);
181  }
182 
190  public function getParams(bool $filter_result = true): array {
191  if (isset($this->filtered_params) && isset($this->unfiltered_params)) {
192  return $filter_result ? $this->filtered_params : $this->unfiltered_params;
193  }
194 
195  $request_overrides = $this->request_overrides;
196  $query = $this->query->all();
197  $attributes = $this->attributes->all();
198  $post = $this->request->all();
199 
200  $this->unfiltered_params = array_merge($post, $attributes, $query, $request_overrides);
201 
202  // filter the input params
203  $this->getContextStack()->push('input');
204  $this->filtered_params = elgg_sanitize_input($this->unfiltered_params);
205  $this->getContextStack()->pop();
206 
207  return $filter_result ? $this->filtered_params : $this->unfiltered_params;
208  }
209 
218  public function getCurrentURL(): string {
219  $url = parse_url(elgg_get_site_url());
220 
221  $page = $url['scheme'] . '://' . $url['host'];
222 
223  if (isset($url['port']) && $url['port']) {
224  $page .= ':' . $url['port'];
225  }
226 
227  $page = trim($page, '/');
228 
229  $page .= $this->getRequestUri();
230 
231  return $page;
232  }
233 
241  public function getUrlSegments(bool $raw = false): array {
242  $path = trim($this->getElggPath(), '/');
243  if (!$raw) {
244  $path = htmlspecialchars($path, ENT_QUOTES, 'UTF-8');
245  }
246 
247  if (empty($path)) {
248  return [];
249  }
250 
251  return explode('/', $path);
252  }
253 
261  public function setUrlSegments(array $segments): Request {
262  $base_path = trim($this->getBasePath(), '/');
263  $server = $this->server->all();
264  $server['REQUEST_URI'] = "$base_path/" . implode('/', $segments);
265 
266  return $this->duplicate(null, null, null, null, null, $server);
267  }
268 
276  public function getFirstUrlSegment(): string {
277  $segments = $this->getUrlSegments();
278  if (!empty($segments)) {
279  return array_shift($segments);
280  }
281 
282  return '';
283  }
284 
290  public function getElggPath(): string {
291  if (PHP_SAPI === 'cli-server') {
292  $path = $this->getRequestUri();
293  } else {
294  $path = $this->getPathInfo();
295  }
296 
297  return preg_replace('~(\?.*)$~', '', $path);
298  }
299 
303  public function getClientIp(): ?string {
304  $ip = parent::getClientIp();
305 
306  if ($ip == $this->server->get('REMOTE_ADDR')) {
307  // try one more
308  $ip_addresses = $this->server->get('HTTP_X_REAL_IP');
309  if ($ip_addresses) {
310  $ip_addresses = explode(',', $ip_addresses);
311 
312  return array_pop($ip_addresses);
313  }
314  }
315 
316  return $ip;
317  }
318 
322  public function isXmlHttpRequest(): bool {
323  return (strtolower($this->headers->get('X-Requested-With') ?: '') === 'xmlhttprequest'
324  || $this->query->get('X-Requested-With') === 'XMLHttpRequest'
325  || $this->request->get('X-Requested-With') === 'XMLHttpRequest');
326  }
327 
333  public function sniffElggUrl() {
334  $base_url = $this->getBaseUrl();
335 
336  // baseURL may end with the PHP script
337  if (str_ends_with($base_url, '.php')) {
338  $base_url = dirname($base_url);
339  }
340 
341  $base_url = str_replace('\\', '/', $base_url);
342 
343  return rtrim($this->getSchemeAndHttpHost() . $base_url, '/') . '/';
344  }
345 
351  public function isRewriteCheck(): bool {
352  if ($this->getPathInfo() !== ('/' . self::REWRITE_TEST_TOKEN)) {
353  return false;
354  }
355 
356  if (!$this->get(self::REWRITE_TEST_TOKEN)) {
357  return false;
358  }
359 
360  return true;
361  }
362 
369  public function isAction(): bool {
370  return $this->getFirstUrlSegment() === 'action';
371  }
372 
378  public function isCliServer(): bool {
379  return PHP_SAPI === 'cli-server';
380  }
381 
389  public function isCliServable(string $root): bool {
390  $file = rtrim($root, '\\/') . $this->getElggPath();
391  if (!is_file($file)) {
392  return false;
393  }
394 
395  // http://php.net/manual/en/features.commandline.webserver.php
396  $extensions = '.3gp, .apk, .avi, .bmp, .css, .csv, .doc, .docx, .flac, .gif, .gz, .gzip, .htm, .html, .ics,';
397  $extensions .= ' .jpe, .jpeg, .jpg, .js, .kml, .kmz, .m4a, .mjs, .mov, .mp3, .mp4, .mpeg, .mpg, .odp, .ods, .odt,';
398  $extensions .= ' .oga, .ogg, .ogv, .pdf, .pdf, .png, .pps, .pptx, .qt, .svg, .swf, .tar, .text, .tif, .txt,';
399  $extensions .= ' .wav, .webm, .wmv, .xls, .xlsx, .xml, .xsl, .xsd, and .zip';
400 
401  // The CLI server routes ALL requests here (even existing files), so we have to check for these.
402  $ext = pathinfo($file, PATHINFO_EXTENSION);
403  if (!$ext) {
404  return false;
405  }
406 
407  $ext = preg_quote($ext, '~');
408 
409  return (bool) preg_match("~\\.{$ext}[,$]~", $extensions);
410  }
411 
419  public function getFiles(string $input_name): array {
420  $files = $this->files->get($input_name);
421  if (empty($files)) {
422  return [];
423  }
424 
425  if (!is_array($files)) {
426  $files = [$files];
427  }
428 
429  return $files;
430  }
431 
440  public function getFile(string $input_name, bool $check_for_validity = true): ?UploadedFile {
441  $files = $this->getFiles($input_name);
442  if (empty($files)) {
443  return null;
444  }
445 
446  $file = $files[0];
447  if (empty($file)) {
448  return null;
449  }
450 
451  if ($check_for_validity && !$file->isValid()) {
452  return null;
453  }
454 
455  return $file;
456  }
457 
464  public function validate(): void {
465  $this->validateRequestHostHeader();
466  $this->validateRequestBodyTruncated();
467  }
468 
478  protected function validateRequestHostHeader(): void {
479  $config = _elgg_services()->config;
480  if (empty($config->wwwroot)) {
481  return;
482  }
483 
484  $config_host = parse_url($config->wwwroot, PHP_URL_HOST);
485  if ($config_host === $this->getHost()) {
486  return;
487  }
488 
489  throw new BadRequestException(elgg_echo('BadRequestException:invalid_host_header'));
490  }
491 
499  protected function validateRequestBodyTruncated(): void {
500  $reported_bytes = $this->server->get('CONTENT_LENGTH');
501 
502  // Requests with multipart content type
503  $post_data_count = count($this->request->all());
504 
505  // Requests with other content types
506  $content = $this->getContent();
507  $post_body_length = is_string($content) ? elgg_strlen($content) : 0;
508 
509  $file_count = count($this->files->all());
510 
511  $is_valid = function() use ($reported_bytes, $post_data_count, $post_body_length, $file_count) {
512  if (empty($reported_bytes)) {
513  // Content length is set for POST requests only
514  return true;
515  }
516 
517  if (empty($post_data_count) && empty($post_body_length) && empty($file_count)) {
518  // The size of $_POST or uploaded files has exceed the size limit
519  // and the request body/query has been truncated
520  // thus the request reported bytes is set, but no postdata is found
521  return false;
522  }
523 
524  return true;
525  };
526 
527  if ($is_valid()) {
528  return;
529  }
530 
531  throw new BadRequestException(elgg_echo('actiongatekeeper:uploadexceeded'));
532  }
533 
543  public function correctBaseURL(\Elgg\Config $config): void {
544  if (\Elgg\Application::isCli()) {
545  return;
546  }
547 
548  $path = parse_url($config->wwwroot, PHP_URL_PATH);
549 
550  $this->baseUrl = rtrim($path, '/');
551  }
552 }
$content
Set robots.txt action.
Definition: set_robots.php:6
$context_stack
Definition: add.php:36
$context
Definition: add.php:8
return[ 'admin/delete_admin_notices'=>['access'=> 'admin'], 'admin/menu/save'=>['access'=> 'admin'], 'admin/plugins/activate'=>['access'=> 'admin'], 'admin/plugins/activate_all'=>['access'=> 'admin'], 'admin/plugins/deactivate'=>['access'=> 'admin'], 'admin/plugins/deactivate_all'=>['access'=> 'admin'], 'admin/plugins/set_priority'=>['access'=> 'admin'], 'admin/security/security_txt'=>['access'=> 'admin'], 'admin/security/settings'=>['access'=> 'admin'], 'admin/security/regenerate_site_secret'=>['access'=> 'admin'], 'admin/site/cache/invalidate'=>['access'=> 'admin'], 'admin/site/flush_cache'=>['access'=> 'admin'], 'admin/site/icons'=>['access'=> 'admin'], 'admin/site/set_maintenance_mode'=>['access'=> 'admin'], 'admin/site/set_robots'=>['access'=> 'admin'], 'admin/site/theme'=>['access'=> 'admin'], 'admin/site/unlock_upgrade'=>['access'=> 'admin'], 'admin/site/settings'=>['access'=> 'admin'], 'admin/upgrade'=>['access'=> 'admin'], 'admin/upgrade/reset'=>['access'=> 'admin'], 'admin/user/ban'=>['access'=> 'admin'], 'admin/user/bulk/ban'=>['access'=> 'admin'], 'admin/user/bulk/delete'=>['access'=> 'admin'], 'admin/user/bulk/unban'=>['access'=> 'admin'], 'admin/user/bulk/validate'=>['access'=> 'admin'], 'admin/user/change_email'=>['access'=> 'admin'], 'admin/user/delete'=>['access'=> 'admin'], 'admin/user/login_as'=>['access'=> 'admin'], 'admin/user/logout_as'=>[], 'admin/user/makeadmin'=>['access'=> 'admin'], 'admin/user/resetpassword'=>['access'=> 'admin'], 'admin/user/removeadmin'=>['access'=> 'admin'], 'admin/user/unban'=>['access'=> 'admin'], 'admin/user/validate'=>['access'=> 'admin'], 'annotation/delete'=>[], 'avatar/upload'=>[], 'comment/save'=>[], 'diagnostics/download'=>['access'=> 'admin'], 'entity/chooserestoredestination'=>[], 'entity/delete'=>[], 'entity/mute'=>[], 'entity/restore'=>[], 'entity/subscribe'=>[], 'entity/trash'=>[], 'entity/unmute'=>[], 'entity/unsubscribe'=>[], 'login'=>['access'=> 'logged_out'], 'logout'=>[], 'notifications/mute'=>['access'=> 'public'], 'plugins/settings/remove'=>['access'=> 'admin'], 'plugins/settings/save'=>['access'=> 'admin'], 'plugins/usersettings/save'=>[], 'register'=>['access'=> 'logged_out', 'middleware'=>[\Elgg\Router\Middleware\RegistrationAllowedGatekeeper::class,],], 'river/delete'=>[], 'settings/notifications'=>[], 'settings/notifications/subscriptions'=>[], 'user/changepassword'=>['access'=> 'public'], 'user/requestnewpassword'=>['access'=> 'public'], 'useradd'=>['access'=> 'admin'], 'usersettings/save'=>[], 'widgets/add'=>[], 'widgets/delete'=>[], 'widgets/move'=>[], 'widgets/save'=>[],]
Definition: actions.php:73
$attributes
Elgg AJAX loader.
Definition: ajax_loader.php:10
$query
Load, boot, and implement a front controller for an Elgg application.
Definition: Application.php:47
Manages a global stack of strings for sharing information about the current execution context.
Definition: Context.php:27
Thrown when request is malformatted.
Elgg HTTP request.
Definition: Request.php:17
sniffElggUrl()
Sniff the Elgg site URL with trailing slash.
Definition: Request.php:333
getFile(string $input_name, bool $check_for_validity=true)
Returns the first file found based on the input name.
Definition: Request.php:440
__construct(array $query=[], array $request=[], array $attributes=[], array $cookies=[], array $files=[], array $server=[], $content=null)
{}
Definition: Request.php:56
isCliServer()
Is PHP running the CLI server front controller.
Definition: Request.php:378
validate()
Validate the request.
Definition: Request.php:464
isCliServable(string $root)
Is the request pointing to a file that the CLI server can handle?
Definition: Request.php:389
getUrlSegments(bool $raw=false)
Get the Elgg URL segments.
Definition: Request.php:241
const REWRITE_TEST_OUTPUT
Definition: Request.php:20
validateRequestHostHeader()
Validate that the request was made on the correct host.
Definition: Request.php:478
setParam(string $key, $value, bool $override_request=false)
Sets an input value that may later be retrieved by get_input.
Definition: Request.php:150
initializeTrustedProxyConfiguration(Config $config)
Configure trusted proxy servers to allow access to more client information.
Definition: Request.php:79
validateRequestBodyTruncated()
Validate that the request body hasn't been truncated (eg.
Definition: Request.php:499
bool $_integration_testing
Definition: Request.php:26
getElggPath()
Get the Request URI minus querystring.
Definition: Request.php:290
correctBaseURL(\Elgg\Config $config)
Correct the base URL of the request.
Definition: Request.php:543
isAction()
Is the request an action.
Definition: Request.php:369
setUrlSegments(array $segments)
Get a cloned request with new Elgg URL segments.
Definition: Request.php:261
getFirstUrlSegment()
Get first Elgg URL segment.
Definition: Request.php:276
isRewriteCheck()
Is the request for checking URL rewriting?
Definition: Request.php:351
getParam(string $key, $default=null, bool $filter_result=true)
Get some input from variables passed submitted through GET or POST.
Definition: Request.php:177
getContextStack()
Returns context stack.
Definition: Request.php:110
initializeContext()
Initialize context stack.
Definition: Request.php:98
getCurrentURL()
Returns current page URL.
Definition: Request.php:218
getFiles(string $input_name)
Returns an array of uploaded file objects regardless of upload status/errors.
Definition: Request.php:419
getRoute()
Returns the route matched for this request by the router.
Definition: Request.php:135
const REWRITE_TEST_TOKEN
Definition: Request.php:19
setRoute(Route $route)
Sets the route matched for this request by the router.
Definition: Request.php:121
getParams(bool $filter_result=true)
Returns all values parsed from the request.
Definition: Request.php:190
Route Wrapper.
Definition: Route.php:8
getMatchedParameters()
Get matched parameters.
Definition: Route.php:34
elgg_get_site_url()
Get the URL for the current (or specified) site, ending with "/".
$input_name
Definition: crop.php:24
foreach($plugin_guids as $guid) if(empty($deactivated_plugins)) $url
Definition: deactivate.php:39
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
$extensions
if(! $pagination && $limit !==false &&!empty($items) &&count($items) >=$limit) $base_url
Definition: list.php:114
_elgg_services()
Get the global service provider.
Definition: elgglib.php:337
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:240
elgg_sanitize_input($input)
Filter input from a given string based on registered events.
Definition: input.php:77
$value
Definition: generic.php:51
$default
Definition: checkbox.php:30
elgg_echo(string $message_key, array $args=[], string $language='')
Elgg language module Functions to manage language and translations.
Definition: languages.php:17
$request
Definition: livesearch.php:12
elgg_strlen()
Wrapper function for mb_strlen().
Definition: mb_wrapper.php:53
$path
Definition: details.php:70
if($container instanceof ElggGroup && $container->guid !=elgg_get_page_owner_guid()) $key
Definition: summary.php:44
if(parse_url(elgg_get_site_url(), PHP_URL_PATH) !=='/') if(file_exists(elgg_get_root_path() . 'robots.txt'))
Set robots.txt.
Definition: robots.php:10
$page
Definition: admin.php:24
$segments
Definition: admin.php:13