You are here

s3fs_cors.post_object_v4.class.inc in S3 File System CORS Upload 7

The code of classes was taken from the AWS SDK v3.180.3 and a bit modified. Next classes are used here:

File

s3fs_cors.post_object_v4.class.inc
View source
<?php

/**
 * @file
 * The code of classes was taken from the AWS SDK v3.180.3 and a bit modified.
 * Next classes are used here:
 *  - https://github.com/aws/aws-sdk-php/blob/3.180.3/src/S3/PostObjectV4.php
 *  - https://github.com/aws/aws-sdk-php/blob/3.180.3/src/Signature/SignatureTrait.php
 */
use Aws\Common\Client\AbstractClient as S3ClientInterface;
use Aws\Common\Credentials\CredentialsInterface;
use Guzzle\Http\Url;

/**
 * Encapsulates the logic for getting the data for an S3 object POST upload form
 *
 * @link http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPOST.html
 * @link http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
 */
class S3fsCorsPostObjectV4 {
  use S3fsCorsSignatureTrait;
  private $client;
  private $bucket;
  private $formAttributes;
  private $formInputs;

  /**
   * Constructs the PostObject.
   *
   * The options array accepts the following keys:
   * @link http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
   *
   * @param S3ClientInterface $client     Client used with the POST object
   * @param string            $bucket     Bucket to use
   * @param array             $formInputs Associative array of form input
   *                                      fields.
   * @param array             $options    Policy condition options
   * @param mixed             $expiration Upload expiration time value. By
   *                                      default: 1 hour valid period.
   */
  public function __construct(S3ClientInterface $client, $bucket, array $formInputs, array $options = [], $expiration = '+1 hours') {
    $this->client = $client;
    $this->bucket = $bucket;

    // setup form attributes
    $this->formAttributes = [
      'action' => $this
        ->generateUri(),
      'method' => 'POST',
      'enctype' => 'multipart/form-data',
    ];
    $credentials = $this->client
      ->getCredentials();
    if ($securityToken = $credentials
      ->getSecurityToken()) {
      $options[] = [
        'x-amz-security-token' => $securityToken,
      ];
      $formInputs['X-Amz-Security-Token'] = $securityToken;
    }

    // setup basic policy
    $policy = [
      'expiration' => gmdate('Y-m-d\\TH:i:s\\Z', strtotime($expiration)),
      'conditions' => $options,
    ];

    // setup basic formInputs
    $this->formInputs = $formInputs + [
      'key' => '${filename}',
    ];

    // finalize policy and signature
    $this->formInputs += $this
      ->getPolicyAndSignature($credentials, $policy);
  }

  /**
   * Gets the S3 client.
   *
   * @return S3ClientInterface
   */
  public function getClient() {
    return $this->client;
  }

  /**
   * Gets the bucket name.
   *
   * @return string
   */
  public function getBucket() {
    return $this->bucket;
  }

  /**
   * Gets the form attributes as an array.
   *
   * @return array
   */
  public function getFormAttributes() {
    return $this->formAttributes;
  }

  /**
   * Set a form attribute.
   *
   * @param string $attribute Form attribute to set.
   * @param string $value     Value to set.
   */
  public function setFormAttribute($attribute, $value) {
    $this->formAttributes[$attribute] = $value;
  }

  /**
   * Gets the form inputs as an array.
   *
   * @return array
   */
  public function getFormInputs() {
    return $this->formInputs;
  }

  /**
   * Set a form input.
   *
   * @param string $field Field name to set
   * @param string $value Value to set.
   */
  public function setFormInput($field, $value) {
    $this->formInputs[$field] = $value;
  }
  private function generateUri() {
    $url = Url::factory($this->client
      ->getBaseUrl());
    if ($url
      ->getScheme() === 'https' && strpos($this->bucket, '.') !== false) {

      // Use path-style URLs
      $url
        ->setPath($this->bucket);
    }
    else {

      // Use virtual-style URLs
      $url
        ->setHost($this->bucket . '.' . $url
        ->getHost());
    }
    return (string) $url;
  }
  protected function getPolicyAndSignature(CredentialsInterface $credentials, array $policy) {
    $ldt = gmdate('Ymd\\THis\\Z');

    // SignatureV4::ISO8601_BASIC
    $sdt = substr($ldt, 0, 8);
    $policy['conditions'][] = [
      'X-Amz-Date' => $ldt,
    ];
    $region = $this->client
      ->getRegion();
    $scope = $this
      ->createScope($sdt, $region, 's3');
    $creds = "{$credentials->getAccessKeyId()}/{$scope}";
    $policy['conditions'][] = [
      'X-Amz-Credential' => $creds,
    ];
    $policy['conditions'][] = [
      'X-Amz-Algorithm' => "AWS4-HMAC-SHA256",
    ];
    $jsonPolicy64 = base64_encode(json_encode($policy));
    $key = $this
      ->getSigningKey($sdt, $region, 's3', $credentials
      ->getSecretKey());
    return [
      'X-Amz-Credential' => $creds,
      'X-Amz-Algorithm' => "AWS4-HMAC-SHA256",
      'X-Amz-Date' => $ldt,
      'Policy' => $jsonPolicy64,
      'X-Amz-Signature' => bin2hex(hash_hmac('sha256', $jsonPolicy64, $key, true)),
    ];
  }

}

/**
 * Provides signature calculation for SignatureV4.
 */
trait S3fsCorsSignatureTrait {

  /** @var array Cache of previously signed values */
  private $cache = [];

  /** @var int Size of the hash cache */
  private $cacheSize = 0;
  private function createScope($shortDate, $region, $service) {
    return "{$shortDate}/{$region}/{$service}/aws4_request";
  }
  private function getSigningKey($shortDate, $region, $service, $secretKey) {
    $k = $shortDate . '_' . $region . '_' . $service . '_' . $secretKey;
    if (!isset($this->cache[$k])) {

      // Clear the cache when it reaches 50 entries
      if (++$this->cacheSize > 50) {
        $this->cache = [];
        $this->cacheSize = 0;
      }
      $dateKey = hash_hmac('sha256', $shortDate, "AWS4{$secretKey}", true);
      $regionKey = hash_hmac('sha256', $region, $dateKey, true);
      $serviceKey = hash_hmac('sha256', $service, $regionKey, true);
      $this->cache[$k] = hash_hmac('sha256', 'aws4_request', $serviceKey, true);
    }
    return $this->cache[$k];
  }

}

Classes

Namesort descending Description
S3fsCorsPostObjectV4 Encapsulates the logic for getting the data for an S3 object POST upload form

Traits

Namesort descending Description
S3fsCorsSignatureTrait Provides signature calculation for SignatureV4.