You are here

public function S3fsStream::getExternalUrl in S3 File System 8.3

Same name and namespace in other branches
  1. 8.2 src/StreamWrapper/S3fsStream.php \Drupal\s3fs\StreamWrapper\S3fsStream::getExternalUrl()
  2. 4.0.x src/StreamWrapper/S3fsStream.php \Drupal\s3fs\StreamWrapper\S3fsStream::getExternalUrl()

Returns a web accessible URL for the resource.

The format of the returned URL will be different depending on how the S3 integration has been configured on the S3 File System admin page.

Return value

string A web accessible URL for the resource.

Overrides StreamWrapperInterface::getExternalUrl

1 method overrides S3fsStream::getExternalUrl()
PrivateS3fsStream::getExternalUrl in src/StreamWrapper/PrivateS3fsStream.php
Returns a web accessible URL for the resource.

File

src/StreamWrapper/S3fsStream.php, line 364

Class

S3fsStream
Defines a Drupal s3 (s3://) stream wrapper class.

Namespace

Drupal\s3fs\StreamWrapper

Code

public function getExternalUrl() {

  // In case we're on Windows, replace backslashes with forward-slashes.
  // Note that $uri is the unaltered value of the File's URI, while
  // $s3_key may be changed at various points to account for implementation
  // details on the S3 side (e.g. root_folder, s3fs-public).
  $s3_key = str_replace('\\', '/', $this->streamWrapperManager::getTarget($this->uri));

  // Is this an ImageStyle Path.
  $isImageStylePath = FALSE;

  // When generating an image derivative URL, e.g. styles/thumbnail/blah.jpg,
  // if the file doesn't exist, provide a URL to s3fs's special version of
  // image_style_deliver(), which will create the derivative when that URL
  // gets requested.  When the file does exist we need to calculate an itok
  // in case the link requires presigning.
  $path_parts = explode('/', $s3_key);
  if ($path_parts[0] == 'styles' && substr($s3_key, -4) != '.css') {
    $isImageStylePath = TRUE;

    // Style derivative does not yet exist in the bucket.
    if (!$this
      ->getS3fsObject($this->uri)) {

      // The style delivery path looks like: s3/files/styles/thumbnail/...
      // And $path_parts looks like ['styles', 'thumbnail', ...],
      // so just prepend s3/files/.
      array_unshift($path_parts, 's3', 'files');
      $path = implode('/', $path_parts);
      return $GLOBALS['base_url'] . '/' . UrlHelper::encodePath($path);
    }

    // Generate itok key in case link that need to be presigned.
    $suppressItok = \Drupal::config('image.settings')
      ->get('suppress_itok_output');
    if (!$suppressItok) {
      $imageStyleName = $path_parts[1];
      $srcScheme = $path_parts[2];

      // Strip off 'style', ImageStyleName and scheme.
      array_splice($path_parts, 0, 3);
      $srcImageUri = $srcScheme . '://' . implode('/', $path_parts);
      $itok = \Drupal::entityTypeManager()
        ->getStorage('image_style')
        ->load($imageStyleName)
        ->getPathToken($srcImageUri);
    }
  }

  // Deal with public:// files.
  if (StreamWrapperManager::getScheme($this->uri) == 'public') {

    // public:// files are stored in S3 inside the s3fs_public_folder.
    $public_folder = !empty($this->config['public_folder']) ? $this->config['public_folder'] : 's3fs-public';

    // If use_cname check if we are striping the path.
    if (!empty($this->config['use_cname'])) {
      if (!empty($this->config['domain_root']) && $this->config['domain_root'] !== 'public') {
        $s3_key = "{$public_folder}/{$s3_key}";
      }
    }
    else {
      $s3_key = "{$public_folder}/{$s3_key}";
    }
  }

  // Set up the URL settings as speciied in our settings page.
  $url_settings = [
    'torrent' => FALSE,
    'presigned_url' => FALSE,
    'timeout' => 60,
    'forced_saveas' => FALSE,
    'api_args' => [],
    'custom_GET_args' => [],
  ];

  // Presigned URLs.
  foreach ($this->presignedURLs as $blob => $timeout) {

    // ^ is used as the delimeter because it's an illegal character in URLs.
    if (preg_match("^{$blob}^", $s3_key)) {
      $url_settings['presigned_url'] = TRUE;
      $url_settings['timeout'] = $timeout;
      break;
    }
  }

  // Forced Save As.
  foreach ($this->saveas as $blob) {
    if (preg_match("^{$blob}^", $s3_key)) {
      $filename = basename($s3_key);
      $url_settings['api_args']['ResponseContentDisposition'] = "attachment; filename=\"{$filename}\"";
      $url_settings['forced_saveas'] = TRUE;
      break;
    }
  }

  // Allow other modules to change the URL settings.
  \Drupal::moduleHandler()
    ->alter('s3fs_url_settings', $url_settings, $s3_key);

  // Prepend root_folder if necessary.
  if (!empty($this->config['root_folder'])) {

    // When using cname only append if domain_root is none.
    if (!empty($this->config['use_cname'])) {
      if (!empty($this->config['domain_root']) && $this->config['domain_root'] === 'none') {
        $s3_key = "{$this->config['root_folder']}/{$s3_key}";
      }
    }
    else {
      $s3_key = "{$this->config['root_folder']}/{$s3_key}";
    }
  }
  $commandSettings = [
    'Bucket' => $this->config['bucket'],
    'Key' => $s3_key,
  ];

  // Handle presign expire timeout.
  $expires = NULL;
  if ($url_settings['presigned_url']) {
    $expires = "+{$url_settings['timeout']} seconds";
  }
  else {

    // Due to Amazon's security policies (see Request client parameters @
    // http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html),
    // only signed requests can use request parameters.
    // Thus, we must provide an expiry time for any URLs which specify
    // Response* API args.
    foreach ($url_settings['api_args'] as $key => $arg) {
      if (strpos($key, 'Response') === 0) {

        // @see https://aws.amazon.com/premiumsupport/knowledge-center/presigned-url-s3-bucket-expiration/
        // Max limit: Instance Credential 6h, STS 3d, IAM 7d.
        $expires = "21600 seconds";
        break;
      }
    }
  }

  // If this file is versioned, attach the version number to
  // ensure that browser caches will be bypassed upon version changes.
  $meta = $this
    ->readCache($this->uri);
  if (!empty($meta['version'])) {
    $commandSettings['VersionId'] = $meta['version'];
  }
  foreach ($url_settings['api_args'] as $key => $arg) {
    $commandSettings[$key] = $arg;
  }
  try {
    $command = $this->s3
      ->getCommand('GetObject', $commandSettings);
  } catch (\Exception $e) {
    watchdog_exception('S3fs', $e);
    return Url::fromUserInput('/')
      ->toString();
  }

  // Make sure the url scheme is set correctly.
  $scheme = !empty($this->config['use_https']) ? 'https' : 'http';
  $command
    ->getHandlerList()
    ->appendBuild(Middleware::mapRequest(function (RequestInterface $request) use ($scheme) {
    $uri = $request
      ->getUri();
    if ($uri
      ->getPort() == 80 || $uri
      ->getPort() == 443) {

      // Reset port value for use with scheme select.
      $uri = $uri
        ->withPort(NULL);
    }
    $uri = $uri
      ->withScheme($scheme);
    return $request
      ->withUri($uri);
  }), 'set-scheme');
  if (!empty($url_settings['custom_GET_args'])) {

    // If another module added a 'custom_GET_args' array to the url settings,
    // add a build handler to process them.
    $command
      ->getHandlerList()
      ->appendBuild(Middleware::mapRequest(function (RequestInterface $request) use ($url_settings) {
      $uri = $request
        ->getUri()
        ->withQueryValues($request
        ->getUri(), $url_settings['custom_GET_args']);
      return $request
        ->withUri($uri, TRUE);
    }), 'add-getargs');
  }
  if (!empty($this->config['use_cname'])) {
    $cname = $this->cname;
    $command
      ->getHandlerList()
      ->appendBuild(Middleware::mapRequest(function (RequestInterface $request) use ($cname) {
      $uri = $request
        ->getUri()
        ->withHost($cname['host']);
      if (!empty($cname['port'])) {
        $uri = $uri
          ->withPort($cname['port']);
      }
      return $request
        ->withUri($uri);
    }), 'use-cname');
  }
  if ($isImageStylePath && !$suppressItok) {
    $command
      ->getHandlerList()
      ->appendBuild(Middleware::mapRequest(function (RequestInterface $request) use ($itok) {
      $uri = $request
        ->getUri();
      $uri = $uri
        ->withQueryValue($uri, 'itok', $itok);
      return $request
        ->withUri($uri);
    }), 'add-itok');
  }
  if (!empty($expires)) {

    // Need to use a presign URL.
    try {
      $external_url = (string) $this->s3
        ->createPresignedRequest($command, $expires)
        ->getUri();
    } catch (\Exception $e) {
      watchdog_exception('S3FS', $e);
      return Url::fromUserInput('/')
        ->toString();
    }
    if ($isImageStylePath && !$suppressItok) {
      $parsedUrl = UrlHelper::parse($external_url);
      $queryParams = UrlHelper::filterQueryParameters($parsedUrl['query'], [
        'itok',
      ]);
      $external_url = $parsedUrl['path'] . '?' . UrlHelper::buildQuery($queryParams);
    }
  }
  else {

    // No special request given, we can generate the link.
    if (empty($this->config['use_cname'])) {
      try {
        $external_url = $this->s3
          ->getObjectUrl($this->config['bucket'], $s3_key);
      } catch (\Exception $e) {
        watchdog_exception('S3FS', $e);
        return Url::fromUserInput('/')
          ->toString();
      }
      if (empty($this->config['use_https'])) {

        // Forced HTTPS not enabled and not an HTTPS page load.
        $external_url = preg_replace('#^https:#', 'http:', $external_url);
      }
    }
    else {
      $external_url = $this->cname['scheme'] . '://' . $this->cname['host'];
      if (!empty($this->cname['port'])) {
        $external_url = $external_url . ':' . $this->cname['port'];
      }
      if ($this->config['use_path_style_endpoint']) {
        $external_url = $external_url . '/' . $this->config['bucket'];
      }
      $external_url = $external_url . '/' . UrlHelper::encodePath($s3_key);
    }
    if (!empty($meta['version'])) {
      $external_url = $this
        ->appendGetArg($external_url, 'VersionId', $meta['version']);
    }
    foreach ($url_settings['custom_GET_args'] as $name => $value) {
      $external_url = $this
        ->appendGetArg($external_url, $name, $value);
    }

    // Torrents can only be created for publicly-accessible files:
    // https://forums.aws.amazon.com/thread.jspa?threadID=140949
    foreach ($this->torrents as $blob) {
      if (preg_match("^{$blob}^", $s3_key)) {

        // You get a torrent URL by adding a "torrent" GET arg.
        $external_url = $this
          ->appendGetArg($external_url, 'torrent');
        break;
      }
    }
  }
  return $external_url;
}