You are here

class DomainRedirectResponse in Domain Access 8

A redirect response which understands domain URLs are local to the install.

This class can be used in cases where LocalRedirectResponse needs to be domain sensitive. The main implementation is in DomainSourceRedirectResponseSubscriber.

This class combines LocalAwareRedirectResponseTrait and UrlHelper methods that cannot be overridden safely otherwise.

Hierarchy

Expanded class hierarchy of DomainRedirectResponse

4 files declare their use of DomainRedirectResponse
DomainSourceRedirectResponseSubscriber.php in domain_source/src/EventSubscriber/DomainSourceRedirectResponseSubscriber.php
DomainSourceRedirectResponseSubscriberD8.php in domain_source/src/EventSubscriber/DomainSourceRedirectResponseSubscriberD8.php
DomainSubscriber.php in domain/src/EventSubscriber/DomainSubscriber.php
domain_source.module in domain_source/domain_source.module
Domain-based path rewrites for content.

File

domain/src/DomainRedirectResponse.php, line 20

Namespace

Drupal\domain
View source
class DomainRedirectResponse extends CacheableSecuredRedirectResponse {

  /**
   * The request context.
   *
   * @var \Drupal\Core\Routing\RequestContext
   */
  protected $requestContext;

  /**
   * The trusted host patterns.
   *
   * @var array
   */
  protected static $trustedHostPatterns;

  /**
   * The trusted hosts matched by the settings.
   *
   * @var array
   */
  protected static $trustedHosts;

  /**
   * {@inheritdoc}
   */
  protected function isLocal($url) {
    $base_url = $this
      ->getRequestContext()
      ->getCompleteBaseUrl();
    return !UrlHelper::isExternal($url) || UrlHelper::externalIsLocal($url, $base_url) || $this
      ->externalIsRegistered($url, $base_url);
  }

  /**
   * {@inheritdoc}
   */
  protected function isSafe($url) {
    return $this
      ->isLocal($url);
  }

  /**
   * Returns the request context.
   *
   * @return \Drupal\Core\Routing\RequestContext
   *   The request context.
   */
  protected function getRequestContext() {
    if (!isset($this->requestContext)) {
      $this->requestContext = \Drupal::service('router.request_context');
    }
    return $this->requestContext;
  }

  /**
   * Sets the request context.
   *
   * @param \Drupal\Core\Routing\RequestContext $request_context
   *   The request context.
   *
   * @return $this
   */
  public function setRequestContext(RequestContext $request_context) {
    $this->requestContext = $request_context;
    return $this;
  }

  /**
   * Determines if an external URL points to this domain-aware installation.
   *
   * This method replaces the logic in
   * Drupal\Component\Utility\UrlHelper::externalIsLocal(). Since that class is
   * not directly extendable, we have to replace it.
   *
   * @param string $url
   *   A string containing an external URL, such as "http://example.com/foo".
   * @param string $base_url
   *   The base URL string to check against, such as "http://example.com/".
   *
   * @return bool
   *   TRUE if the URL has the same domain and base path.
   *
   * @throws \InvalidArgumentException
   *   Exception thrown when $url is not fully qualified.
   */
  public static function externalIsRegistered($url, $base_url) {
    $url_parts = parse_url($url);
    $base_parts = parse_url($base_url);
    if (empty($url_parts['host'])) {
      throw new \InvalidArgumentException('A path was passed when a fully qualified domain was expected.');
    }

    // Check that the host name is registered with trusted hosts.
    $trusted = self::checkTrustedHost($url_parts['host']);
    if (!$trusted) {
      return FALSE;
    }

    // Check that the requested $url is registered.
    $negotiator = \Drupal::service('domain.negotiator');
    $registered_domain = $negotiator
      ->isRegisteredDomain($url_parts['host']);
    if (!isset($url_parts['path']) || !isset($base_parts['path'])) {
      return $registered_domain;
    }
    else {

      // When comparing base paths, we need a trailing slash to make sure a
      // partial URL match isn't occurring. Since base_path() always returns
      // with a trailing slash, we don't need to add the trailing slash here.
      return $registered_domain && stripos($url_parts['path'], $base_parts['path']) === 0;
    }
  }

  /**
   * Checks that a host is registered with trusted_host_patterns.
   *
   * This method is cribbed from Symfony's Request::getHost() method.
   *
   * @param string $host
   *   The hostname to check.
   *
   * @return bool
   *   TRUE if the hostname matches the trusted_host_patterns. FALSE otherwise.
   *   It is the caller's responsibility to deal with this result securely.
   */
  public static function checkTrustedHost($host) {

    // See Request::setTrustedHosts();
    if (!isset(self::$trustedHostPatterns)) {
      self::$trustedHostPatterns = array_map(function ($hostPattern) {
        return sprintf('#%s#i', $hostPattern);
      }, Settings::get('trusted_host_patterns', []));

      // Reset the trusted host match array.
      self::$trustedHosts = [];
    }

    // Trim and remove port number from host. Host is lowercase as per RFC
    // 952/2181.
    $host = mb_strtolower(preg_replace('/:\\d+$/', '', trim($host)));

    // In the original Symfony code, hostname validation runs here. We have
    // removed that portion because Domains are already validated on creation.
    if (count(self::$trustedHostPatterns) > 0) {

      // To avoid host header injection attacks, you should provide a list of
      // trusted host patterns.
      if (in_array($host, self::$trustedHosts)) {
        return TRUE;
      }
      foreach (self::$trustedHostPatterns as $pattern) {
        if (preg_match($pattern, $host)) {
          self::$trustedHosts[] = $host;
          return TRUE;
        }
      }
      return FALSE;
    }

    // In cases where trusted_host_patterns are not set, allow all. This is
    // flagged as a security issue by Drupal core in the Reports UI.
    return TRUE;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
CacheableResponseTrait::$cacheabilityMetadata protected property The cacheability metadata.
CacheableResponseTrait::addCacheableDependency public function
CacheableResponseTrait::getCacheableMetadata public function
CacheableSecuredRedirectResponse::fromResponse protected function Copies over the values from the given response. Overrides SecuredRedirectResponse::fromResponse
DomainRedirectResponse::$requestContext protected property The request context.
DomainRedirectResponse::$trustedHostPatterns protected static property The trusted host patterns.
DomainRedirectResponse::$trustedHosts protected static property The trusted hosts matched by the settings.
DomainRedirectResponse::checkTrustedHost public static function Checks that a host is registered with trusted_host_patterns.
DomainRedirectResponse::externalIsRegistered public static function Determines if an external URL points to this domain-aware installation.
DomainRedirectResponse::getRequestContext protected function Returns the request context.
DomainRedirectResponse::isLocal protected function
DomainRedirectResponse::isSafe protected function Returns whether the URL is considered as safe to redirect to. Overrides SecuredRedirectResponse::isSafe
DomainRedirectResponse::setRequestContext public function Sets the request context.
SecuredRedirectResponse::createFromRedirectResponse public static function Copies an existing redirect response into a safe one.
SecuredRedirectResponse::setTargetUrl public function Sets the redirect target of this response.