allow vendord
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m3s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Successful in 32s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m3s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Successful in 32s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
This commit is contained in:
213
vendor/google/cloud-storage/src/Acl.php
vendored
Normal file
213
vendor/google/cloud-storage/src/Acl.php
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2015 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use Google\Cloud\Storage\Connection\ConnectionInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Google Cloud Storage uses access control lists (ACLs) to manage bucket and
|
||||
* object access. ACLs are the mechanism you use to share objects with other
|
||||
* users and allow other users to access your buckets and objects. For more
|
||||
* information please see the overview on
|
||||
* [access-control](https://cloud.google.com/storage/docs/access-control).
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* use Google\Cloud\Storage\StorageClient;
|
||||
*
|
||||
* $storage = new StorageClient();
|
||||
*
|
||||
* $bucket = $storage->bucket('my-bucket');
|
||||
* $acl = $bucket->acl();
|
||||
* ```
|
||||
*/
|
||||
class Acl
|
||||
{
|
||||
const ROLE_READER = 'READER';
|
||||
const ROLE_WRITER = 'WRITER';
|
||||
const ROLE_OWNER = 'OWNER';
|
||||
|
||||
/**
|
||||
* @var ConnectionInterface Represents a connection to Cloud Storage.
|
||||
* @internal
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var array ACL specific options.
|
||||
*/
|
||||
private $aclOptions;
|
||||
|
||||
/**
|
||||
* @param ConnectionInterface $connection Represents a connection to
|
||||
* Cloud Storage. This object is created by StorageClient,
|
||||
* and should not be instantiated outside of this client.
|
||||
* @param string $type The type of access control this instance applies to.
|
||||
* @param array $identity Represents which bucket, file, or generation this
|
||||
* instance applies to.
|
||||
* @throws \InvalidArgumentException Thrown when an invalid type is passed in.
|
||||
*/
|
||||
public function __construct(ConnectionInterface $connection, $type, array $identity)
|
||||
{
|
||||
$validTypes = [
|
||||
'bucketAccessControls',
|
||||
'defaultObjectAccessControls',
|
||||
'objectAccessControls'
|
||||
];
|
||||
|
||||
if (!in_array($type, $validTypes)) {
|
||||
throw new InvalidArgumentException('type must be one of the following: ' . implode(', ', $validTypes));
|
||||
}
|
||||
|
||||
$this->connection = $connection;
|
||||
$this->aclOptions = $identity + ['type' => $type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete access controls.
|
||||
*
|
||||
* Delete access controls on a {@see Bucket} or
|
||||
* {@see StorageObject} for a specified entity.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $acl->delete('allAuthenticatedUsers');
|
||||
* ```
|
||||
*
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/bucketAccessControls/delete BucketAccessControls delete
|
||||
* API documentation.
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/defaultObjectAccessControls/delete
|
||||
* DefaultObjectAccessControls delete API documentation.
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls/delete ObjectAccessControls delete
|
||||
* API documentation.
|
||||
*
|
||||
* @param string $entity The entity to delete.
|
||||
* @param array $options [optional] Configuration Options.
|
||||
* @return void
|
||||
*/
|
||||
public function delete($entity, array $options = [])
|
||||
{
|
||||
$aclOptions = $this->aclOptions + ['entity' => $entity];
|
||||
$this->connection->deleteAcl($options + $aclOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access controls.
|
||||
*
|
||||
* Get access controls on a {@see Bucket} or
|
||||
* {@see StorageObject}. By default this will return all available
|
||||
* access controls. You may optionally specify a single entity to return
|
||||
* details for as well.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $res = $acl->get(['entity' => 'allAuthenticatedUsers']);
|
||||
* ```
|
||||
*
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/bucketAccessControls/get BucketAccessControls get API
|
||||
* documentation.
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/defaultObjectAccessControls/get
|
||||
* DefaultObjectAccessControls get API documentation.
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls/get ObjectAccessControls get API
|
||||
* documentation.
|
||||
*
|
||||
* @param array $options [optional] {
|
||||
* Configuration options.
|
||||
*
|
||||
* @type string $entity The entity to fetch.
|
||||
* }
|
||||
* @return array
|
||||
*/
|
||||
public function get(array $options = [])
|
||||
{
|
||||
if (isset($options['entity'])) {
|
||||
return $this->connection->getAcl($options + $this->aclOptions);
|
||||
}
|
||||
|
||||
$response = $this->connection->listAcl($options + $this->aclOptions);
|
||||
return $response['items'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add access controls.
|
||||
*
|
||||
* Add access controls on a {@see Bucket} or
|
||||
* {@see StorageObject}.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $acl->add('allAuthenticatedUsers', 'WRITER');
|
||||
* ```
|
||||
*
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/bucketAccessControls/insert BucketAccessControls insert
|
||||
* API documentation.
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/defaultObjectAccessControls/insert
|
||||
* DefaultObjectAccessControls insert API documentation.
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls/insert ObjectAccessControls insert
|
||||
* API documentation.
|
||||
*
|
||||
* @param string $entity The entity to add access controls to.
|
||||
* @param string $role The permissions to add for the specified entity. May
|
||||
* be one of 'OWNER', 'READER', or 'WRITER'.
|
||||
* @param array $options [optional] Configuration Options.
|
||||
* @return array
|
||||
*/
|
||||
public function add($entity, $role, array $options = [])
|
||||
{
|
||||
$aclOptions = $this->aclOptions + [
|
||||
'entity' => $entity,
|
||||
'role' => $role
|
||||
];
|
||||
|
||||
return $this->connection->insertAcl($options + $aclOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update access controls.
|
||||
*
|
||||
* Update access controls on a {@see Bucket} or {@see StorageObject}.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $acl->update('allAuthenticatedUsers', 'READER');
|
||||
* ```
|
||||
*
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/bucketAccessControls/patch BucketAccessControls patch API
|
||||
* documentation.
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/defaultObjectAccessControls/patch
|
||||
* DefaultObjectAccessControls patch API documentation.
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls/patch ObjectAccessControls patch
|
||||
* API documentation.
|
||||
*
|
||||
* @param string $entity The entity to update access controls for.
|
||||
* @param string $role The permissions to update for the specified entity.
|
||||
* May be one of 'OWNER', 'READER', or 'WRITER'.
|
||||
* @param array $options [optional] Configuration Options.
|
||||
* @return array
|
||||
*/
|
||||
public function update($entity, $role, array $options = [])
|
||||
{
|
||||
$aclOptions = $this->aclOptions + [
|
||||
'entity' => $entity,
|
||||
'role' => $role
|
||||
];
|
||||
|
||||
return $this->connection->patchAcl($options + $aclOptions);
|
||||
}
|
||||
}
|
||||
1771
vendor/google/cloud-storage/src/Bucket.php
vendored
Normal file
1771
vendor/google/cloud-storage/src/Bucket.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
50
vendor/google/cloud-storage/src/BucketIterator.php
vendored
Normal file
50
vendor/google/cloud-storage/src/BucketIterator.php
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2025 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use ArrayObject;
|
||||
use Google\Cloud\Core\Iterator\ItemIterator;
|
||||
use Google\Cloud\Core\Iterator\PageIterator;
|
||||
|
||||
/**
|
||||
* Iterates over a set of buckets.
|
||||
*
|
||||
* Use the `unreachable()` method to get a list of bucket names that could not
|
||||
* be retrieved. This is only populated when the `returnPartialSuccess` is set to true
|
||||
*/
|
||||
class BucketIterator extends ItemIterator
|
||||
{
|
||||
/**
|
||||
* @param PageIterator $iterator
|
||||
* @param ArrayObject $unreachable
|
||||
*/
|
||||
public function __construct(PageIterator $iterator, private ArrayObject $unreachable)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of unreachable buckets.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function unreachable()
|
||||
{
|
||||
return $this->unreachable->getArrayCopy();
|
||||
}
|
||||
}
|
||||
207
vendor/google/cloud-storage/src/Connection/ConnectionInterface.php
vendored
Normal file
207
vendor/google/cloud-storage/src/Connection/ConnectionInterface.php
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2015 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage\Connection;
|
||||
|
||||
/**
|
||||
* Represents a connection to
|
||||
* [Cloud Storage](https://cloud.google.com/storage/).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function deleteAcl(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getAcl(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function listAcl(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function insertAcl(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function patchAcl(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function deleteBucket(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function restoreBucket(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getBucket(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function listBuckets(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function insertBucket(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getBucketIamPolicy(array $args);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function setBucketIamPolicy(array $args);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function testBucketIamPermissions(array $args);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function patchBucket(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function deleteObject(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function restoreObject(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function copyObject(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function rewriteObject(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function moveObject(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function composeObject(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getObject(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function listObjects(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function patchObject(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function downloadObject(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function insertObject(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getNotification(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function deleteNotification(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function insertNotification(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function listNotifications(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getServiceAccount(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function lockRetentionPolicy(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function createHmacKey(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function deleteHmacKey(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getHmacKey(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function updateHmacKey(array $args = []);
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function listHmacKeys(array $args = []);
|
||||
}
|
||||
72
vendor/google/cloud-storage/src/Connection/IamBucket.php
vendored
Normal file
72
vendor/google/cloud-storage/src/Connection/IamBucket.php
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2016 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage\Connection;
|
||||
|
||||
use Google\Cloud\Core\Iam\IamConnectionInterface;
|
||||
|
||||
/**
|
||||
* IAM Implementation for GCS Buckets
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class IamBucket implements IamConnectionInterface
|
||||
{
|
||||
/**
|
||||
* @var ConnectionInterface
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* @param ConnectionInterface $connection
|
||||
*/
|
||||
public function __construct(ConnectionInterface $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getPolicy(array $args)
|
||||
{
|
||||
if (isset($args['requestedPolicyVersion'])) {
|
||||
$args['optionsRequestedPolicyVersion'] = $args['requestedPolicyVersion'];
|
||||
unset($args['requestedPolicyVersion']);
|
||||
}
|
||||
|
||||
return $this->connection->getBucketIamPolicy($args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function setPolicy(array $args)
|
||||
{
|
||||
unset($args['resource']);
|
||||
return $this->connection->setBucketIamPolicy($args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function testPermissions(array $args)
|
||||
{
|
||||
unset($args['resource']);
|
||||
return $this->connection->testBucketIamPermissions($args);
|
||||
}
|
||||
}
|
||||
915
vendor/google/cloud-storage/src/Connection/Rest.php
vendored
Normal file
915
vendor/google/cloud-storage/src/Connection/Rest.php
vendored
Normal file
@@ -0,0 +1,915 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2015 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage\Connection;
|
||||
|
||||
use Google\Auth\GetUniverseDomainInterface;
|
||||
use Google\Cloud\Core\RequestBuilder;
|
||||
use Google\Cloud\Core\RequestWrapper;
|
||||
use Google\Cloud\Core\RestTrait;
|
||||
use Google\Cloud\Core\Retry;
|
||||
use Google\Cloud\Core\Upload\AbstractUploader;
|
||||
use Google\Cloud\Core\Upload\MultipartUploader;
|
||||
use Google\Cloud\Core\Upload\ResumableUploader;
|
||||
use Google\Cloud\Core\Upload\StreamableUploader;
|
||||
use Google\Cloud\Core\UriTrait;
|
||||
use Google\Cloud\Storage\StorageClient;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Psr7\MimeType;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
/**
|
||||
* Implementation of the
|
||||
* [Google Cloud Storage JSON API](https://cloud.google.com/storage/docs/json_api/).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Rest implements ConnectionInterface
|
||||
{
|
||||
use RestTrait {
|
||||
send as private traitSend;
|
||||
}
|
||||
use RetryTrait;
|
||||
use UriTrait;
|
||||
|
||||
/**
|
||||
* Header and value that helps us identify a transcoded obj
|
||||
* w/o making a metadata(info) call.
|
||||
*/
|
||||
private const TRANSCODED_OBJ_HEADER_KEY = 'X-Goog-Stored-Content-Encoding';
|
||||
private const TRANSCODED_OBJ_HEADER_VAL = 'gzip';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const BASE_URI = 'https://storage.googleapis.com/storage/v1/';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const DEFAULT_API_ENDPOINT = 'https://storage.googleapis.com';
|
||||
|
||||
const DEFAULT_API_ENDPOINT_TEMPLATE = 'https://storage.UNIVERSE_DOMAIN';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const UPLOAD_URI = 'https://storage.googleapis.com/upload/storage/v1/b/{bucket}/o{?query*}';
|
||||
|
||||
const UPLOAD_PATH = 'upload/storage/v1/b/{bucket}/o{?query*}';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const DOWNLOAD_URI = 'https://storage.googleapis.com/storage/v1/b/{bucket}/o/{object}{?query*}';
|
||||
|
||||
const DOWNLOAD_PATH = 'storage/v1/b/{bucket}/o/{object}{?query*}';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $projectId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $apiEndpoint;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
* value null accepted
|
||||
*/
|
||||
private $restRetryFunction;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $retryStrategy;
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
private $restDelayFunction;
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
private $restCalcDelayFunction;
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
private $restRetryListener;
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$config += [
|
||||
'serviceDefinitionPath' => __DIR__ . '/ServiceDefinition/storage-v1.json',
|
||||
'componentVersion' => StorageClient::VERSION,
|
||||
'apiEndpoint' => null,
|
||||
// If the user has not supplied a universe domain, use the environment variable if set.
|
||||
// Otherwise, use the default ("googleapis.com").
|
||||
'universeDomain' => getenv('GOOGLE_CLOUD_UNIVERSE_DOMAIN')
|
||||
?: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN,
|
||||
// Cloud Storage needs to provide a default scope because the Storage
|
||||
// API does not accept JWTs with "audience"
|
||||
'scopes' => StorageClient::FULL_CONTROL_SCOPE,
|
||||
];
|
||||
|
||||
$this->apiEndpoint = $this->getApiEndpoint(null, $config, self::DEFAULT_API_ENDPOINT_TEMPLATE);
|
||||
|
||||
$this->setRequestWrapper(new RequestWrapper($config));
|
||||
$this->setRequestBuilder(new RequestBuilder(
|
||||
$config['serviceDefinitionPath'],
|
||||
$this->apiEndpoint
|
||||
));
|
||||
|
||||
$this->projectId = $this->pluck('projectId', $config, false);
|
||||
$this->restRetryFunction = (isset($config['restRetryFunction'])) ? $config['restRetryFunction'] : null;
|
||||
$this->retryStrategy = $config['retryStrategy'] ?? null;
|
||||
$this->restDelayFunction = $config['restDelayFunction'] ?? null;
|
||||
$this->restCalcDelayFunction = $config['restCalcDelayFunction'] ?? null;
|
||||
$this->restRetryListener = $config['restRetryListener'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function projectId()
|
||||
{
|
||||
return $this->projectId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function deleteAcl(array $args = [])
|
||||
{
|
||||
return $this->send($args['type'], 'delete', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getAcl(array $args = [])
|
||||
{
|
||||
return $this->send($args['type'], 'get', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function listAcl(array $args = [])
|
||||
{
|
||||
return $this->send($args['type'], 'list', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function insertAcl(array $args = [])
|
||||
{
|
||||
return $this->send($args['type'], 'insert', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function patchAcl(array $args = [])
|
||||
{
|
||||
return $this->send($args['type'], 'patch', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function deleteBucket(array $args = [])
|
||||
{
|
||||
return $this->send('buckets', 'delete', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function restoreBucket(array $args = [])
|
||||
{
|
||||
return $this->send('buckets', 'restore', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getBucket(array $args = [])
|
||||
{
|
||||
return $this->send('buckets', 'get', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function listBuckets(array $args = [])
|
||||
{
|
||||
return $this->send('buckets', 'list', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function insertBucket(array $args = [])
|
||||
{
|
||||
return $this->send('buckets', 'insert', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function patchBucket(array $args = [])
|
||||
{
|
||||
return $this->send('buckets', 'patch', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function deleteObject(array $args = [])
|
||||
{
|
||||
return $this->send('objects', 'delete', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function restoreObject(array $args = [])
|
||||
{
|
||||
return $this->send('objects', 'restore', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function copyObject(array $args = [])
|
||||
{
|
||||
return $this->send('objects', 'copy', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function rewriteObject(array $args = [])
|
||||
{
|
||||
return $this->send('objects', 'rewrite', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function moveObject(array $args = [])
|
||||
{
|
||||
return $this->send('objects', 'move', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function composeObject(array $args = [])
|
||||
{
|
||||
return $this->send('objects', 'compose', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getObject(array $args = [])
|
||||
{
|
||||
return $this->send('objects', 'get', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function listObjects(array $args = [])
|
||||
{
|
||||
return $this->send('objects', 'list', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function patchObject(array $args = [])
|
||||
{
|
||||
return $this->send('objects', 'patch', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function downloadObject(array $args = [])
|
||||
{
|
||||
// This makes sure we honour the range headers specified by the user
|
||||
$requestedBytes = $this->getRequestedBytes($args);
|
||||
$resultStream = Utils::streamFor(null);
|
||||
$transcodedObj = false;
|
||||
|
||||
$args['retryStrategy'] ??= $this->retryStrategy;
|
||||
|
||||
list($request, $requestOptions) = $this->buildDownloadObjectParams($args);
|
||||
|
||||
$invocationId = Uuid::uuid4()->toString();
|
||||
$requestOptions['retryHeaders'] = self::getRetryHeaders($invocationId, 1);
|
||||
$requestOptions['restRetryFunction'] = $this->getRestRetryFunction('objects', 'get', $args);
|
||||
// We try to deduce if the object is a transcoded object when we receive the headers.
|
||||
$requestOptions['restOptions']['on_headers'] = function ($response) use (&$transcodedObj) {
|
||||
$header = $response->getHeader(self::TRANSCODED_OBJ_HEADER_KEY);
|
||||
if (is_array($header) && in_array(self::TRANSCODED_OBJ_HEADER_VAL, $header)) {
|
||||
$transcodedObj = true;
|
||||
}
|
||||
};
|
||||
$attempt = null;
|
||||
$requestOptions['restRetryListener'] = function (
|
||||
\Exception $e,
|
||||
$retryAttempt,
|
||||
&$arguments
|
||||
) use (
|
||||
$resultStream,
|
||||
$requestedBytes,
|
||||
$invocationId,
|
||||
&$attempt,
|
||||
) {
|
||||
// if the exception has a response for us to use
|
||||
if ($e instanceof RequestException
|
||||
&& $e->hasResponse()
|
||||
&& $e->getResponse()->getStatusCode() >= 200
|
||||
&& $e->getResponse()->getStatusCode() < 300
|
||||
) {
|
||||
$msg = (string) $e->getResponse()->getBody();
|
||||
|
||||
$fetchedStream = Utils::streamFor($msg);
|
||||
|
||||
// add the partial response to our stream that we will return
|
||||
Utils::copyToStream($fetchedStream, $resultStream);
|
||||
|
||||
// Start from the byte that was last fetched
|
||||
$startByte = intval($requestedBytes['startByte']) + $resultStream->getSize();
|
||||
$endByte = $requestedBytes['endByte'];
|
||||
|
||||
// modify the range headers to fetch the remaining data
|
||||
$arguments[1]['headers']['Range'] = sprintf('bytes=%s-%s', $startByte, $endByte);
|
||||
$arguments[0] = $this->modifyRequestForRetry($arguments[0], $retryAttempt, $invocationId);
|
||||
|
||||
// Copy the final result to the end of the stream
|
||||
$attempt = $retryAttempt;
|
||||
}
|
||||
};
|
||||
|
||||
$fetchedStream = $this->requestWrapper->send(
|
||||
$request,
|
||||
$requestOptions
|
||||
)->getBody();
|
||||
|
||||
// If no retry attempt was made, then we can return the stream as is.
|
||||
// This is important in the case where downloadObject is called to open
|
||||
// the file but not to read from it yet.
|
||||
if ($attempt === null) {
|
||||
return $fetchedStream;
|
||||
}
|
||||
|
||||
// If our object is a transcoded object, then Range headers are not honoured.
|
||||
// That means even if we had a partial download available, the final obj
|
||||
// that was fetched will contain the complete object. So, we don't need to copy
|
||||
// the partial stream, we can just return the stream we fetched.
|
||||
if ($transcodedObj) {
|
||||
return $fetchedStream;
|
||||
}
|
||||
|
||||
Utils::copyToStream($fetchedStream, $resultStream);
|
||||
|
||||
$resultStream->seek(0);
|
||||
return $resultStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
* @experimental The experimental flag means that while we believe this method
|
||||
* or class is ready for use, it may change before release in backwards-
|
||||
* incompatible ways. Please use with caution, and test thoroughly when
|
||||
* upgrading.
|
||||
*/
|
||||
public function downloadObjectAsync(array $args = [])
|
||||
{
|
||||
list($request, $requestOptions) = $this->buildDownloadObjectParams($args);
|
||||
|
||||
return $this->requestWrapper->sendAsync(
|
||||
$request,
|
||||
$requestOptions
|
||||
)->then(function (ResponseInterface $response) {
|
||||
return $response->getBody();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function insertObject(array $args = [])
|
||||
{
|
||||
$args = $this->resolveUploadOptions($args);
|
||||
|
||||
$uploadType = AbstractUploader::UPLOAD_TYPE_RESUMABLE;
|
||||
if ($args['streamable']) {
|
||||
$uploaderClass = StreamableUploader::class;
|
||||
} elseif ($args['resumable']) {
|
||||
$uploaderClass = ResumableUploader::class;
|
||||
} else {
|
||||
$uploaderClass = MultipartUploader::class;
|
||||
$uploadType = AbstractUploader::UPLOAD_TYPE_MULTIPART;
|
||||
}
|
||||
|
||||
$uriParams = [
|
||||
'bucket' => $args['bucket'],
|
||||
'query' => [
|
||||
'predefinedAcl' => $args['predefinedAcl'],
|
||||
'uploadType' => $uploadType,
|
||||
'userProject' => $args['userProject']
|
||||
]
|
||||
];
|
||||
|
||||
// Passing the preconditions we want to extract out of arguments
|
||||
// into our query params.
|
||||
$preconditions = self::$condIdempotentOps['objects.insert'];
|
||||
foreach ($preconditions as $precondition) {
|
||||
if (isset($args[$precondition])) {
|
||||
$uriParams['query'][$precondition] = $args[$precondition];
|
||||
}
|
||||
}
|
||||
|
||||
return new $uploaderClass(
|
||||
$this->requestWrapper,
|
||||
$args['data'],
|
||||
$this->expandUri($this->apiEndpoint . self::UPLOAD_PATH, $uriParams),
|
||||
$args['uploaderOptions']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
private function resolveUploadOptions(array $args)
|
||||
{
|
||||
$args += [
|
||||
'bucket' => null,
|
||||
'name' => null,
|
||||
'validate' => true,
|
||||
'resumable' => null,
|
||||
'streamable' => null,
|
||||
'predefinedAcl' => null,
|
||||
'metadata' => [],
|
||||
'userProject' => null,
|
||||
];
|
||||
|
||||
$args['retryStrategy'] ??= $this->retryStrategy;
|
||||
|
||||
$args['data'] = Utils::streamFor($args['data']);
|
||||
|
||||
if ($args['resumable'] === null) {
|
||||
$args['resumable'] = $args['data']->getSize() > AbstractUploader::RESUMABLE_LIMIT;
|
||||
}
|
||||
|
||||
if (!$args['name']) {
|
||||
$args['name'] = basename($args['data']->getMetadata('uri'));
|
||||
}
|
||||
|
||||
$validate = $this->chooseValidationMethod($args);
|
||||
if ($validate === 'md5') {
|
||||
$args['metadata']['md5Hash'] = base64_encode(Utils::hash($args['data'], 'md5', true));
|
||||
} elseif ($validate === 'crc32') {
|
||||
$args['metadata']['crc32c'] = $this->crcFromStream($args['data']);
|
||||
}
|
||||
|
||||
$args['metadata']['name'] = $args['name'];
|
||||
if (isset($args['retention'])) {
|
||||
// during object creation retention properties go into metadata
|
||||
// but not into request body
|
||||
$args['metadata']['retention'] = $args['retention'];
|
||||
unset($args['retention']);
|
||||
}
|
||||
unset($args['name']);
|
||||
$args['contentType'] = $args['metadata']['contentType']
|
||||
?? MimeType::fromFilename($args['metadata']['name']);
|
||||
|
||||
$uploaderOptionKeys = [
|
||||
'restOptions',
|
||||
'retries',
|
||||
'requestTimeout',
|
||||
'chunkSize',
|
||||
'contentType',
|
||||
'metadata',
|
||||
'uploadProgressCallback',
|
||||
'restDelayFunction',
|
||||
'restCalcDelayFunction',
|
||||
];
|
||||
|
||||
$args['uploaderOptions'] = array_intersect_key($args, array_flip($uploaderOptionKeys));
|
||||
$args = array_diff_key($args, array_flip($uploaderOptionKeys));
|
||||
|
||||
// Passing on custom retry function to $args['uploaderOptions']
|
||||
$retryFunc = $this->getRestRetryFunction(
|
||||
'objects',
|
||||
'insert',
|
||||
$args
|
||||
);
|
||||
$args['uploaderOptions']['restRetryFunction'] = $retryFunc;
|
||||
|
||||
$args['uploaderOptions'] = $this->addRetryHeaderLogic(
|
||||
$args['uploaderOptions']
|
||||
);
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getBucketIamPolicy(array $args)
|
||||
{
|
||||
return $this->send('buckets', 'getIamPolicy', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function setBucketIamPolicy(array $args)
|
||||
{
|
||||
return $this->send('buckets', 'setIamPolicy', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function testBucketIamPermissions(array $args)
|
||||
{
|
||||
return $this->send('buckets', 'testIamPermissions', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getNotification(array $args = [])
|
||||
{
|
||||
return $this->send('notifications', 'get', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function deleteNotification(array $args = [])
|
||||
{
|
||||
return $this->send('notifications', 'delete', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function insertNotification(array $args = [])
|
||||
{
|
||||
return $this->send('notifications', 'insert', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function listNotifications(array $args = [])
|
||||
{
|
||||
return $this->send('notifications', 'list', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getServiceAccount(array $args = [])
|
||||
{
|
||||
return $this->send('projects.resources.serviceAccount', 'get', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function lockRetentionPolicy(array $args = [])
|
||||
{
|
||||
return $this->send('buckets', 'lockRetentionPolicy', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function createHmacKey(array $args = [])
|
||||
{
|
||||
return $this->send('projects.resources.hmacKeys', 'create', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function deleteHmacKey(array $args = [])
|
||||
{
|
||||
return $this->send('projects.resources.hmacKeys', 'delete', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function getHmacKey(array $args = [])
|
||||
{
|
||||
return $this->send('projects.resources.hmacKeys', 'get', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function updateHmacKey(array $args = [])
|
||||
{
|
||||
return $this->send('projects.resources.hmacKeys', 'update', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*/
|
||||
public function listHmacKeys(array $args = [])
|
||||
{
|
||||
return $this->send('projects.resources.hmacKeys', 'list', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
* @return array
|
||||
*/
|
||||
private function buildDownloadObjectParams(array $args)
|
||||
{
|
||||
$args += [
|
||||
'bucket' => null,
|
||||
'object' => null,
|
||||
'generation' => null,
|
||||
'userProject' => null
|
||||
];
|
||||
|
||||
$requestOptions = array_intersect_key($args, [
|
||||
'restOptions' => null,
|
||||
'retries' => null,
|
||||
'restRetryFunction' => null,
|
||||
'restCalcDelayFunction' => null,
|
||||
'restDelayFunction' => null
|
||||
]);
|
||||
|
||||
$queryOptions = [
|
||||
'generation' => $args['generation'],
|
||||
'alt' => 'media',
|
||||
'userProject' => $args['userProject'],
|
||||
];
|
||||
if (isset($args['softDeleted'])) {
|
||||
// alt param cannot be specified with softDeleted param. See:
|
||||
// https://cloud.google.com/storage/docs/json_api/v1/objects/get
|
||||
unset($args['alt']);
|
||||
$queryOptions['softDeleted'] = $args['softDeleted'];
|
||||
}
|
||||
|
||||
$uri = $this->expandUri($this->apiEndpoint . self::DOWNLOAD_PATH, [
|
||||
'bucket' => $args['bucket'],
|
||||
'object' => $args['object'],
|
||||
'query' => $queryOptions,
|
||||
]);
|
||||
|
||||
return [
|
||||
new Request('GET', Utils::uriFor($uri)),
|
||||
$requestOptions
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose an upload validation method based on user input and platform
|
||||
* requirements.
|
||||
*
|
||||
* @param array $args
|
||||
* @return bool|string
|
||||
*/
|
||||
private function chooseValidationMethod(array $args)
|
||||
{
|
||||
// If the user provided a hash, skip hashing.
|
||||
if (isset($args['metadata']['md5Hash']) || isset($args['metadata']['crc32c'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$validate = $args['validate'];
|
||||
if (in_array($validate, [false, 'crc32', 'md5'], true)) {
|
||||
return $validate;
|
||||
}
|
||||
|
||||
// not documented, but the feature is called crc32c, so let's accept that as input anyways.
|
||||
if ($validate === 'crc32c') {
|
||||
return 'crc32';
|
||||
}
|
||||
|
||||
// is the extension loaded?
|
||||
if ($this->crc32cExtensionLoaded()) {
|
||||
return 'crc32';
|
||||
}
|
||||
|
||||
// is crc32c available in `hash()`?
|
||||
if ($this->supportsBuiltinCrc32c()) {
|
||||
return 'crc32';
|
||||
}
|
||||
|
||||
return 'md5';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a CRC32c checksum from a stream.
|
||||
*
|
||||
* @param StreamInterface $data
|
||||
* @return string
|
||||
*/
|
||||
private function crcFromStream(StreamInterface $data)
|
||||
{
|
||||
$pos = $data->tell();
|
||||
$data->rewind();
|
||||
$crc32c = hash_init('crc32c');
|
||||
while (!$data->eof()) {
|
||||
$buffer = $data->read(1048576);
|
||||
hash_update($crc32c, $buffer);
|
||||
}
|
||||
$data->seek($pos);
|
||||
$hash = hash_final($crc32c, true);
|
||||
return base64_encode($hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the crc32c extension is available.
|
||||
*
|
||||
* Protected access for unit testing.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function crc32cExtensionLoaded()
|
||||
{
|
||||
return extension_loaded('crc32c');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if hash() supports crc32c.
|
||||
*
|
||||
* @return bool
|
||||
* @deprecated
|
||||
*/
|
||||
protected function supportsBuiltinCrc32c()
|
||||
{
|
||||
return extension_loaded('hash') && in_array('crc32c', hash_algos());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the required retry function and send the request.
|
||||
*
|
||||
* @param string $resource resource name, eg: buckets.
|
||||
* @param string $method method name, eg: get
|
||||
* @param array $options [optional] Options used to build out the request.
|
||||
* @param array $whitelisted [optional]
|
||||
*/
|
||||
public function send($resource, $method, array $options = [], $whitelisted = false)
|
||||
{
|
||||
$retryMap = [
|
||||
'projects.resources.serviceAccount' => 'serviceaccount',
|
||||
'projects.resources.hmacKeys' => 'hmacKey',
|
||||
'bucketAccessControls' => 'bucket_acl',
|
||||
'defaultObjectAccessControls' => 'default_object_acl',
|
||||
'objectAccessControls' => 'object_acl'
|
||||
];
|
||||
$retryResource = isset($retryMap[$resource]) ? $retryMap[$resource] : $resource;
|
||||
|
||||
$options['restRetryFunction'] = $this->restRetryFunction ?? $this->getRestRetryFunction(
|
||||
$retryResource,
|
||||
$method,
|
||||
$options
|
||||
);
|
||||
|
||||
$options += array_filter([
|
||||
'retryStrategy' => $this->retryStrategy,
|
||||
'restDelayFunction' => $this->restDelayFunction,
|
||||
'restCalcDelayFunction' => $this->restCalcDelayFunction,
|
||||
'restRetryListener' => $this->restRetryListener,
|
||||
]);
|
||||
|
||||
$options = $this->addRetryHeaderLogic($options);
|
||||
|
||||
return $this->traitSend($resource, $method, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the retry headers to $args which amends retry hash and attempt
|
||||
* count to the required header.
|
||||
* @param array $args
|
||||
* @return array
|
||||
*/
|
||||
private function addRetryHeaderLogic(array $args)
|
||||
{
|
||||
$invocationId = Uuid::uuid4()->toString();
|
||||
$args['retryHeaders'] = self::getRetryHeaders($invocationId, 1);
|
||||
|
||||
$userListener = $args['restRetryListener'] ?? null;
|
||||
|
||||
// Adding callback logic to update headers while retrying
|
||||
$args['restRetryListener'] = function (
|
||||
\Exception $e,
|
||||
$retryAttempt,
|
||||
&$arguments
|
||||
) use (
|
||||
$invocationId,
|
||||
$userListener
|
||||
) {
|
||||
$arguments[0] = $this->modifyRequestForRetry(
|
||||
$arguments[0],
|
||||
$retryAttempt,
|
||||
$invocationId
|
||||
);
|
||||
|
||||
if ($userListener) {
|
||||
$userListener($e, $retryAttempt, $arguments);
|
||||
}
|
||||
};
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
private function modifyRequestForRetry(
|
||||
RequestInterface $request,
|
||||
int $retryAttempt,
|
||||
string $invocationId
|
||||
) {
|
||||
$changes = self::getRetryHeaders($invocationId, $retryAttempt + 1);
|
||||
$headerLine = $request->getHeaderLine(Retry::RETRY_HEADER_KEY);
|
||||
|
||||
// An associative array to contain final header values as
|
||||
// $headerValueKey => $headerValue
|
||||
$headerElements = [];
|
||||
|
||||
// Adding existing values
|
||||
$headerLineValues = explode(' ', $headerLine);
|
||||
foreach ($headerLineValues as $value) {
|
||||
$key = explode('/', $value)[0];
|
||||
$headerElements[$key] = $value;
|
||||
}
|
||||
|
||||
// Adding changes with replacing value if $key already present
|
||||
foreach ($changes as $change) {
|
||||
$key = explode('/', $change)[0];
|
||||
$headerElements[$key] = $change;
|
||||
}
|
||||
|
||||
return $request->withHeader(
|
||||
Retry::RETRY_HEADER_KEY,
|
||||
implode(' ', $headerElements)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Util function to compute the bytes requested for a download request.
|
||||
*
|
||||
* @param array $options Request options
|
||||
* @return array
|
||||
*/
|
||||
private function getRequestedBytes(array $options)
|
||||
{
|
||||
$startByte = 0;
|
||||
$endByte = '';
|
||||
|
||||
if (isset($options['restOptions']) && isset($options['restOptions']['headers'])) {
|
||||
$headers = $options['restOptions']['headers'];
|
||||
if (isset($headers['Range']) || isset($headers['range'])) {
|
||||
$header = isset($headers['Range']) ? $headers['Range'] : $headers['range'];
|
||||
$range = explode('=', $header);
|
||||
$bytes = explode('-', $range[1]);
|
||||
$startByte = $bytes[0];
|
||||
$endByte = $bytes[1];
|
||||
}
|
||||
}
|
||||
|
||||
return compact('startByte', 'endByte');
|
||||
}
|
||||
}
|
||||
247
vendor/google/cloud-storage/src/Connection/RetryTrait.php
vendored
Normal file
247
vendor/google/cloud-storage/src/Connection/RetryTrait.php
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2022 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage\Connection;
|
||||
|
||||
use Google\Cloud\Storage\StorageClient;
|
||||
|
||||
/**
|
||||
* Trait which provides helper methods for retry logic.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait RetryTrait
|
||||
{
|
||||
/**
|
||||
* The HTTP codes that will be retried by our custom retry function.
|
||||
* @var array
|
||||
*/
|
||||
private static $httpRetryCodes = [
|
||||
0, // connection-refused OR connection-reset gives status code of 0
|
||||
200, // partial download cases
|
||||
408,
|
||||
429,
|
||||
500,
|
||||
502,
|
||||
503,
|
||||
504
|
||||
];
|
||||
|
||||
/**
|
||||
* The operations which can be retried without any conditions
|
||||
* (Idempotent)
|
||||
* @var array
|
||||
*/
|
||||
private static $idempotentOps = [
|
||||
'bucket_acl.get',
|
||||
'bucket_acl.list',
|
||||
'buckets.delete',
|
||||
'buckets.get',
|
||||
'buckets.getIamPolicy',
|
||||
'buckets.insert',
|
||||
'buckets.list',
|
||||
'buckets.lockRetentionPolicy',
|
||||
'buckets.testIamPermissions',
|
||||
'default_object_acl.get',
|
||||
'default_object_acl.list',
|
||||
'hmacKey.delete',
|
||||
'hmacKey.get',
|
||||
'hmacKey.list',
|
||||
'notifications.delete',
|
||||
'notifications.get',
|
||||
'notifications.list',
|
||||
'object_acl.get',
|
||||
'object_acl.list',
|
||||
'objects.get',
|
||||
'objects.list',
|
||||
'serviceaccount.get',
|
||||
'signBlob.execute'
|
||||
];
|
||||
|
||||
/**
|
||||
* The operations which can be retried with specific conditions
|
||||
* (Conditionally idempotent)
|
||||
* @var array
|
||||
*/
|
||||
private static $condIdempotentOps = [
|
||||
'buckets.patch' => ['ifMetagenerationMatch', 'etag'],
|
||||
// Currently etag is not supported, so this preCondition never available
|
||||
'buckets.setIamPolicy' => ['etag'],
|
||||
'buckets.update' => ['ifMetagenerationMatch', 'etag'],
|
||||
'hmacKey.update' => ['etag'],
|
||||
'objects.compose' => ['ifGenerationMatch'],
|
||||
'objects.copy' => ['ifGenerationMatch'],
|
||||
'objects.delete' => ['ifGenerationMatch'],
|
||||
'objects.insert' => ['ifGenerationMatch', 'ifGenerationNotMatch'],
|
||||
'objects.patch' => ['ifMetagenerationMatch', 'etag'],
|
||||
'objects.rewrite' => ['ifGenerationMatch'],
|
||||
'objects.update' => ['ifMetagenerationMatch']
|
||||
];
|
||||
|
||||
/**
|
||||
* Retry strategies which enforce certain behaviour like:
|
||||
* - Always retrying a call when an exception occurs(within the limits of 'max retries').
|
||||
* - Never retrying a call when an exception occurs.
|
||||
* - Retrying only when the operation is considered idempotent(default).
|
||||
* These configurations are supplied for per api call basis.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Header that identifies a specific request hash. The
|
||||
* hash needs to stay the same for multiple retries.
|
||||
*/
|
||||
private static $INVOCATION_ID_HEADER = 'gccl-invocation-id';
|
||||
|
||||
/**
|
||||
* Header that identifies the attempt count for a request. The
|
||||
* value will increment by 1 with every retry.
|
||||
*/
|
||||
private static $ATTEMPT_COUNT_HEADER = 'gccl-attempt-count';
|
||||
|
||||
/**
|
||||
* Return a retry decider function.
|
||||
*
|
||||
* @param string $resource resource name, eg: buckets.
|
||||
* @param string $method method name, eg: get
|
||||
* @param array $args
|
||||
* @return callable
|
||||
*/
|
||||
private function getRestRetryFunction($resource, $method, array $args)
|
||||
{
|
||||
if (isset($args['restRetryFunction'])) {
|
||||
return $args['restRetryFunction'];
|
||||
}
|
||||
$methodName = sprintf('%s.%s', $resource, $method);
|
||||
$isOpIdempotent = in_array($methodName, self::$idempotentOps);
|
||||
$preconditionNeeded = array_key_exists($methodName, self::$condIdempotentOps);
|
||||
$preconditionSupplied = $this->isPreConditionSupplied($methodName, $args);
|
||||
$retryStrategy = isset($args['retryStrategy']) ?
|
||||
$args['retryStrategy'] :
|
||||
StorageClient::RETRY_IDEMPOTENT;
|
||||
|
||||
return function (
|
||||
\Exception $exception,
|
||||
$currentAttempt = 0,
|
||||
$maxRetries = null
|
||||
) use (
|
||||
$isOpIdempotent,
|
||||
$preconditionNeeded,
|
||||
$preconditionSupplied,
|
||||
$retryStrategy
|
||||
) {
|
||||
return $this->retryDeciderFunction(
|
||||
$exception,
|
||||
$isOpIdempotent,
|
||||
$preconditionNeeded,
|
||||
$preconditionSupplied,
|
||||
$retryStrategy,
|
||||
$currentAttempt,
|
||||
$maxRetries
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns true when the user given
|
||||
* precondtions ($preConditions) has values that are present
|
||||
* in the precondition map ($this->condIdempotentMap) for that method.
|
||||
* eg: condIdempotentMap has entry 'objects.copy' => ['ifGenerationMatch'],
|
||||
* if the user has given 'ifGenerationMatch' in the 'objects.copy' operation,
|
||||
* it will be available in the $preConditions
|
||||
* as an array ['ifGenerationMatch']. This makes the array_intersect
|
||||
* function return a non empty result and this function returns true.
|
||||
*
|
||||
* @param string $methodName method name, eg: buckets.get.
|
||||
* @param array $args arguments which include preconditions provided,
|
||||
* eg: ['ifGenerationMatch' => 0].
|
||||
* @return bool
|
||||
*/
|
||||
private function isPreConditionSupplied($methodName, array $args)
|
||||
{
|
||||
if (isset(self::$condIdempotentOps[$methodName])) {
|
||||
// return true if required precondition are given.
|
||||
return !empty(array_intersect(
|
||||
self::$condIdempotentOps[$methodName],
|
||||
array_keys($args)
|
||||
));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide whether the op needs to be retried or not.
|
||||
*
|
||||
* @param \Exception $exception The exception object received
|
||||
* while sending the request.
|
||||
* @param int $currentAttempt Current retry attempt.
|
||||
* @param bool $isIdempotent
|
||||
* @param bool $preconditionNeeded
|
||||
* @param bool $preconditionSupplied
|
||||
* @param int|null $maxRetries The maximum number of retries allowed.
|
||||
* Null for no limit.
|
||||
* @return bool
|
||||
*/
|
||||
private function retryDeciderFunction(
|
||||
\Exception $exception,
|
||||
$isIdempotent,
|
||||
$preconditionNeeded,
|
||||
$preconditionSupplied,
|
||||
$retryStrategy,
|
||||
$currentAttempt = 0,
|
||||
$maxRetries = null
|
||||
) {
|
||||
// If maxRetries is specified, ensure we don't exceed it
|
||||
if ($maxRetries !== null && $currentAttempt >= $maxRetries) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($retryStrategy == StorageClient::RETRY_NEVER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$statusCode = $exception->getCode();
|
||||
// Retry if the exception status code matches
|
||||
// with one of the retriable status code and
|
||||
// the operation is either idempotent or conditionally
|
||||
// idempotent with preconditions supplied.
|
||||
|
||||
if (in_array($statusCode, self::$httpRetryCodes)) {
|
||||
if ($retryStrategy == StorageClient::RETRY_ALWAYS) {
|
||||
return true;
|
||||
} elseif ($isIdempotent) {
|
||||
return true;
|
||||
} elseif ($preconditionNeeded) {
|
||||
return $preconditionSupplied;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility func that returns the list of headers that need to be
|
||||
* attached to every request and its retries.
|
||||
*/
|
||||
private static function getRetryHeaders($invocationId, $attemptCount)
|
||||
{
|
||||
return [
|
||||
sprintf('%s/%s', self::$INVOCATION_ID_HEADER, $invocationId),
|
||||
sprintf('%s/%d', self::$ATTEMPT_COUNT_HEADER, $attemptCount)
|
||||
];
|
||||
}
|
||||
}
|
||||
5918
vendor/google/cloud-storage/src/Connection/ServiceDefinition/storage-v1.json
vendored
Normal file
5918
vendor/google/cloud-storage/src/Connection/ServiceDefinition/storage-v1.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
86
vendor/google/cloud-storage/src/CreatedHmacKey.php
vendored
Normal file
86
vendor/google/cloud-storage/src/CreatedHmacKey.php
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
/**
|
||||
* Represents a newly created HMAC key. Provides access to the key metadata and
|
||||
* secret.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* use Google\Cloud\Storage\StorageClient;
|
||||
*
|
||||
* $storage = new StorageClient();
|
||||
* $response = $storage->createHmacKey($serviceAccountEmail);
|
||||
* ```
|
||||
*/
|
||||
class CreatedHmacKey
|
||||
{
|
||||
/**
|
||||
* @var HmacKey
|
||||
*/
|
||||
private $hmacKey;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $secret;
|
||||
|
||||
/**
|
||||
* @param HmacKey $hmacKey The HMAC Key object.
|
||||
* @param string $secret The HMAC key secret.
|
||||
*/
|
||||
public function __construct(HmacKey $hmacKey, $secret)
|
||||
{
|
||||
$this->hmacKey = $hmacKey;
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HMAC key object.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $key = $response->hmacKey();
|
||||
* ```
|
||||
*
|
||||
* @return HmacKey
|
||||
*/
|
||||
public function hmacKey()
|
||||
{
|
||||
return $this->hmacKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HMAC key secret.
|
||||
*
|
||||
* This value will never be returned from the API after first creation. Make
|
||||
* sure to record it for later use immediately upon key creation.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $secret = $response->secret();
|
||||
* ```
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function secret()
|
||||
{
|
||||
return $this->secret;
|
||||
}
|
||||
}
|
||||
152
vendor/google/cloud-storage/src/EncryptionTrait.php
vendored
Normal file
152
vendor/google/cloud-storage/src/EncryptionTrait.php
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2016 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use phpseclib\Crypt\RSA as RSA2;
|
||||
use phpseclib3\Crypt\RSA as RSA3;
|
||||
|
||||
/**
|
||||
* Trait which provides helper methods for customer-supplied encryption.
|
||||
*/
|
||||
trait EncryptionTrait
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $copySourceEncryptionHeaderNames = [
|
||||
'algorithm' => 'x-goog-copy-source-encryption-algorithm',
|
||||
'key' => 'x-goog-copy-source-encryption-key',
|
||||
'keySHA256' => 'x-goog-copy-source-encryption-key-sha256'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $encryptionHeaderNames = [
|
||||
'algorithm' => 'x-goog-encryption-algorithm',
|
||||
'key' => 'x-goog-encryption-key',
|
||||
'keySHA256' => 'x-goog-encryption-key-sha256'
|
||||
];
|
||||
|
||||
/**
|
||||
* Formats options for customer-supplied encryption headers.
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
* @access private
|
||||
*/
|
||||
public function formatEncryptionHeaders(array $options)
|
||||
{
|
||||
$encryptionHeaders = [];
|
||||
$useCopySourceHeaders = $options['useCopySourceHeaders'] ?? false;
|
||||
$key = $options['encryptionKey'] ?? null;
|
||||
$keySHA256 = $options['encryptionKeySHA256'] ?? null;
|
||||
$destinationKey = $options['destinationEncryptionKey'] ?? null;
|
||||
$destinationKeySHA256 = $options['destinationEncryptionKeySHA256'] ?? null;
|
||||
|
||||
unset($options['useCopySourceHeaders']);
|
||||
unset($options['encryptionKey']);
|
||||
unset($options['encryptionKeySHA256']);
|
||||
unset($options['destinationEncryptionKey']);
|
||||
unset($options['destinationEncryptionKeySHA256']);
|
||||
|
||||
$encryptionHeaders = $this->buildHeaders($key, $keySHA256, $useCopySourceHeaders)
|
||||
+ $this->buildHeaders($destinationKey, $destinationKeySHA256, false);
|
||||
|
||||
if (!empty($encryptionHeaders)) {
|
||||
if (isset($options['restOptions']['headers'])) {
|
||||
$options['restOptions']['headers'] += $encryptionHeaders;
|
||||
} else {
|
||||
$options['restOptions']['headers'] = $encryptionHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds out customer-supplied encryption headers.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $keySHA256
|
||||
* @param bool $useCopySourceHeaders
|
||||
* @return array
|
||||
*/
|
||||
private function buildHeaders($key, $keySHA256, $useCopySourceHeaders)
|
||||
{
|
||||
if ($key) {
|
||||
$headerNames = $useCopySourceHeaders
|
||||
? $this->copySourceEncryptionHeaderNames
|
||||
: $this->encryptionHeaderNames;
|
||||
|
||||
if (!$keySHA256) {
|
||||
$decodedKey = base64_decode($key);
|
||||
$keySHA256 = base64_encode(hash('SHA256', $decodedKey, true));
|
||||
}
|
||||
|
||||
return [
|
||||
$headerNames['algorithm'] => 'AES256',
|
||||
$headerNames['key'] => $key,
|
||||
$headerNames['keySHA256'] => $keySHA256
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a string using a given private key.
|
||||
*
|
||||
* @deprecated Please use the {@see Google\Auth\SignBlobInterface::signBlob()}
|
||||
* and implementations for signing strings.
|
||||
* This method will be removed in a future release.
|
||||
*
|
||||
* @param string $privateKey The private key to use to sign the data.
|
||||
* @param string $data The data to sign.
|
||||
* @param bool $forceOpenssl If true, OpenSSL will be used regardless of
|
||||
* whether phpseclib is available. **Defaults to** `false`.
|
||||
* @return string The signature
|
||||
*/
|
||||
protected function signString($privateKey, $data, $forceOpenssl = false)
|
||||
{
|
||||
$signature = '';
|
||||
|
||||
if (class_exists(RSA3::class) && !$forceOpenssl) {
|
||||
$rsa = RSA3::loadPrivateKey($privateKey);
|
||||
$rsa = $rsa->withPadding(RSA3::SIGNATURE_PKCS1)
|
||||
->withHash('sha256');
|
||||
|
||||
$signature = $rsa->sign($data);
|
||||
} elseif (class_exists(RSA2::class) && !$forceOpenssl) {
|
||||
$rsa = new RSA2();
|
||||
$rsa->loadKey($privateKey);
|
||||
$rsa->setSignatureMode(RSA2::SIGNATURE_PKCS1);
|
||||
$rsa->setHash('sha256');
|
||||
|
||||
$signature = $rsa->sign($data);
|
||||
} elseif (extension_loaded('openssl')) {
|
||||
openssl_sign($data, $signature, $privateKey, 'sha256WithRSAEncryption');
|
||||
} else {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new \RuntimeException('OpenSSL is not installed.');
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
return $signature;
|
||||
}
|
||||
}
|
||||
199
vendor/google/cloud-storage/src/HmacKey.php
vendored
Normal file
199
vendor/google/cloud-storage/src/HmacKey.php
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use Google\Cloud\Storage\Connection\ConnectionInterface;
|
||||
|
||||
/**
|
||||
* Represents a Service Account HMAC key.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* use Google\Cloud\Storage\StorageClient;
|
||||
*
|
||||
* $storage = new StorageClient();
|
||||
* $hmacKey = $storage->hmacKey($accessId);
|
||||
* ```
|
||||
*/
|
||||
class HmacKey
|
||||
{
|
||||
/**
|
||||
* @var ConnectionInterface
|
||||
* @internal
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $projectId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $accessId;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private $info;
|
||||
|
||||
/**
|
||||
* @param ConnectionInterface $connection A connection to Cloud Storage.
|
||||
* This object is created by StorageClient,
|
||||
* and should not be instantiated outside of this client.
|
||||
* @param string $projectId The current project ID.
|
||||
* @param string $accessId The key identifier.
|
||||
* @param array|null $info The key metadata.
|
||||
*/
|
||||
public function __construct(
|
||||
ConnectionInterface $connection,
|
||||
$projectId,
|
||||
$accessId,
|
||||
array $info = []
|
||||
) {
|
||||
$this->connection = $connection;
|
||||
$this->projectId = $projectId;
|
||||
$this->accessId = $accessId;
|
||||
$this->info = $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HMAC Key Access ID.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $accessId = $hmacKey->accessId();
|
||||
* ```
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function accessId()
|
||||
{
|
||||
return $this->accessId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the key metadata from Cloud Storage.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $keyMetadata = $hmacKey->reload();
|
||||
* ```
|
||||
*
|
||||
* @param array $options {
|
||||
* Configuration Options
|
||||
*
|
||||
* @type string $userProject If set, this is the ID of the project which
|
||||
* will be billed for the request. **NOTE**: This option is
|
||||
* currently ignored by Cloud Storage.
|
||||
* }
|
||||
* @return array
|
||||
*/
|
||||
public function reload(array $options = [])
|
||||
{
|
||||
$this->info = $this->connection->getHmacKey([
|
||||
'projectId' => $this->projectId,
|
||||
'accessId' => $this->accessId
|
||||
] + $options);
|
||||
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HMAC Key Metadata.
|
||||
*
|
||||
* If the metadata is not already available, it will be requested from Cloud
|
||||
* Storage.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $keyMetadata = $hmacKey->info();
|
||||
* ```
|
||||
*
|
||||
* @param array $options {
|
||||
* Configuration Options
|
||||
*
|
||||
* @type string $userProject If set, this is the ID of the project which
|
||||
* will be billed for the request. **NOTE**: This option is
|
||||
* currently ignored by Cloud Storage.
|
||||
* }
|
||||
* @return array
|
||||
*/
|
||||
public function info(array $options = [])
|
||||
{
|
||||
return $this->info ?: $this->reload($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the HMAC Key state.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $hmacKey->update('INACTIVE');
|
||||
* ```
|
||||
*
|
||||
* @param string $state The key state. Either `ACTIVE` or `INACTIVE`.
|
||||
* @param array $options {
|
||||
* Configuration Options
|
||||
*
|
||||
* @type string $userProject If set, this is the ID of the project which
|
||||
* will be billed for the request. **NOTE**: This option is
|
||||
* currently ignored by Cloud Storage.
|
||||
* }
|
||||
* @return array
|
||||
*/
|
||||
public function update($state, array $options = [])
|
||||
{
|
||||
$this->info = $this->connection->updateHmacKey([
|
||||
'accessId' => $this->accessId,
|
||||
'projectId' => $this->projectId,
|
||||
'state' => $state
|
||||
] + $options);
|
||||
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the HMAC Key.
|
||||
*
|
||||
* Key state must be set to `INACTIVE` prior to deletion. See
|
||||
* {@see HmacKey::update()} for details.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $hmacKey->delete();
|
||||
* ```
|
||||
*
|
||||
* @param array $options {
|
||||
* Configuration Options
|
||||
*
|
||||
* @type string $userProject If set, this is the ID of the project which
|
||||
* will be billed for the request. **NOTE**: This option is
|
||||
* currently ignored by Cloud Storage.
|
||||
* }
|
||||
* @return void
|
||||
*/
|
||||
public function delete(array $options = [])
|
||||
{
|
||||
$this->connection->deleteHmacKey([
|
||||
'accessId' => $this->accessId,
|
||||
'projectId' => $this->projectId,
|
||||
] + $options);
|
||||
}
|
||||
}
|
||||
477
vendor/google/cloud-storage/src/Lifecycle.php
vendored
Normal file
477
vendor/google/cloud-storage/src/Lifecycle.php
vendored
Normal file
@@ -0,0 +1,477 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2018 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use Google\Cloud\Core\Timestamp;
|
||||
|
||||
/**
|
||||
* Object Lifecycle Management supports common use cases like setting a Time to
|
||||
* Live (TTL) for objects, archiving older versions of objects, or "downgrading"
|
||||
* storage classes of objects to help manage costs.
|
||||
*
|
||||
* This builder does not execute any network requests and is intended to be used
|
||||
* in combination with either
|
||||
* {@see StorageClient::createBucket()}
|
||||
* or {@see Bucket::update()}.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* // Access a builder preconfigured with rules already existing on a given
|
||||
* // bucket.
|
||||
* use Google\Cloud\Storage\StorageClient;
|
||||
*
|
||||
* $storage = new StorageClient();
|
||||
* $bucket = $storage->bucket('my-bucket');
|
||||
* $lifecycle = $bucket->currentLifecycle();
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* // Or get a fresh builder by using the static factory method.
|
||||
* use Google\Cloud\Storage\Bucket;
|
||||
*
|
||||
* $lifecycle = Bucket::lifecycle();
|
||||
* ```
|
||||
*
|
||||
* @see https://cloud.google.com/storage/docs/lifecycle Object Lifecycle Management API Documentation
|
||||
*/
|
||||
class Lifecycle implements \ArrayAccess, \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $lifecycle;
|
||||
|
||||
/**
|
||||
* @param array $lifecycle [optional] A lifecycle configuration. Please see
|
||||
* [here](https://cloud.google.com/storage/docs/json_api/v1/buckets#lifecycle)
|
||||
* for the expected structure.
|
||||
*/
|
||||
public function __construct(array $lifecycle = [])
|
||||
{
|
||||
$this->lifecycle = $lifecycle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Object Lifecycle Delete Rule.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $lifecycle->addDeleteRule([
|
||||
* 'age' => 50,
|
||||
* 'isLive' => true
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param array $condition {
|
||||
* The condition(s) where the rule will apply.
|
||||
*
|
||||
* @type int $age Age of an object (in days). This condition is
|
||||
* satisfied when an object reaches the specified age.
|
||||
* @type \DateTimeInterface|string $createdBefore This condition is
|
||||
* satisfied when an object is created before midnight of the
|
||||
* specified date in UTC. If a string is given, it must be a date
|
||||
* in RFC 3339 format with only the date part (for instance,
|
||||
* "2013-01-15").
|
||||
* @type \DateTimeInterface|string $customTimeBefore This condition is
|
||||
* satisfied when the custom time on an object is before this date
|
||||
* in UTC. If a string is given, it must be a date in RFC 3339
|
||||
* format with only the date part (for instance, "2013-01-15").
|
||||
* @type int $daysSinceCustomTime Number of days elapsed since the
|
||||
* user-specified timestamp set on an object. The condition is
|
||||
* satisfied if the days elapsed is at least this number. If no
|
||||
* custom timestamp is specified on an object, the condition does
|
||||
* not apply.
|
||||
* @type int $daysSinceNoncurrentTime Number of days elapsed since the
|
||||
* noncurrent timestamp of an object. The condition is satisfied
|
||||
* if the days elapsed is at least this number. This condition is
|
||||
* relevant only for versioned objects. The value of the field
|
||||
* must be a nonnegative integer. If it's zero, the object version
|
||||
* will become eligible for Lifecycle action as soon as it becomes
|
||||
* noncurrent.
|
||||
* @type bool $isLive Relevant only for versioned objects. If the value
|
||||
* is `true`, this condition matches live objects; if the value is
|
||||
* `false`, it matches archived objects.
|
||||
* @type string[] $matchesStorageClass Objects having any of the storage
|
||||
* classes specified by this condition will be matched. Values
|
||||
* include `"MULTI_REGIONAL"`, `"REGIONAL"`, `"NEARLINE"`,
|
||||
* `"ARCHIVE"`, `"COLDLINE"`, `"STANDARD"`, and
|
||||
* `"DURABLE_REDUCED_AVAILABILITY"`.
|
||||
* @type \DateTimeInterface|string $noncurrentTimeBefore This condition
|
||||
* is satisfied when the noncurrent time on an object is before
|
||||
* this timestamp. This condition is relevant only for versioned
|
||||
* objects. If a string is given, it must be a date in RFC 3339
|
||||
* format with only the date part (for instance, "2013-01-15").
|
||||
* @type int $numNewerVersions Relevant only for versioned objects. If
|
||||
* the value is N, this condition is satisfied when there are at
|
||||
* least N versions (including the live version) newer than this
|
||||
* version of the object.
|
||||
* @type string[] $matchesPrefix Objects having names which start with
|
||||
* values specified by this condition will be matched.
|
||||
* @type string[] $matchesSuffix Objects having names which end with
|
||||
* values specified by this condition will be matched.
|
||||
* }
|
||||
* @return Lifecycle
|
||||
*/
|
||||
public function addDeleteRule(array $condition)
|
||||
{
|
||||
$this->lifecycle['rule'][] = [
|
||||
'action' => [
|
||||
'type' => 'Delete'
|
||||
],
|
||||
'condition' => $this->formatCondition($condition)
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a AbortIncompleteMultipartUpload lifecycle rule to Object Lifecycle.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $lifecycle->addAbortIncompleteMultipartUploadRule([
|
||||
* 'age' => 50,
|
||||
* 'isLive' => true
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param array $condition {
|
||||
* The condition(s) where the rule will apply.
|
||||
*
|
||||
* @type int $age Age of an object (in days). This condition is
|
||||
* satisfied when an object reaches the specified age.
|
||||
* @type \DateTimeInterface|string $createdBefore This condition is
|
||||
* satisfied when an object is created before midnight of the
|
||||
* specified date in UTC. If a string is given, it must be a date
|
||||
* in RFC 3339 format with only the date part (for instance,
|
||||
* "2013-01-15").
|
||||
* @type \DateTimeInterface|string $customTimeBefore This condition is
|
||||
* satisfied when the custom time on an object is before this date
|
||||
* in UTC. If a string is given, it must be a date in RFC 3339
|
||||
* format with only the date part (for instance, "2013-01-15").
|
||||
* @type int $daysSinceCustomTime Number of days elapsed since the
|
||||
* user-specified timestamp set on an object. The condition is
|
||||
* satisfied if the days elapsed is at least this number. If no
|
||||
* custom timestamp is specified on an object, the condition does
|
||||
* not apply.
|
||||
* @type int $daysSinceNoncurrentTime Number of days elapsed since the
|
||||
* noncurrent timestamp of an object. The condition is satisfied
|
||||
* if the days elapsed is at least this number. This condition is
|
||||
* relevant only for versioned objects. The value of the field
|
||||
* must be a nonnegative integer. If it's zero, the object version
|
||||
* will become eligible for Lifecycle action as soon as it becomes
|
||||
* noncurrent.
|
||||
* @type bool $isLive Relevant only for versioned objects. If the value
|
||||
* is `true`, this condition matches live objects; if the value is
|
||||
* `false`, it matches archived objects.
|
||||
* @type string[] $matchesStorageClass Objects having any of the storage
|
||||
* classes specified by this condition will be matched. Values
|
||||
* include `"MULTI_REGIONAL"`, `"REGIONAL"`, `"NEARLINE"`,
|
||||
* `"ARCHIVE"`, `"COLDLINE"`, `"STANDARD"`, and
|
||||
* `"DURABLE_REDUCED_AVAILABILITY"`.
|
||||
* @type \DateTimeInterface|string $noncurrentTimeBefore This condition
|
||||
* is satisfied when the noncurrent time on an object is before
|
||||
* this timestamp. This condition is relevant only for versioned
|
||||
* objects. If a string is given, it must be a date in RFC 3339
|
||||
* format with only the date part (for instance, "2013-01-15").
|
||||
* @type int $numNewerVersions Relevant only for versioned objects. If
|
||||
* the value is N, this condition is satisfied when there are at
|
||||
* least N versions (including the live version) newer than this
|
||||
* version of the object.
|
||||
* @type string[] $matchesPrefix Objects having names which start with
|
||||
* values specified by this condition will be matched.
|
||||
* @type string[] $matchesSuffix Objects having names which end with
|
||||
* values specified by this condition will be matched.
|
||||
* }
|
||||
* @return Lifecycle
|
||||
*/
|
||||
public function addAbortIncompleteMultipartUploadRule(array $condition)
|
||||
{
|
||||
$this->lifecycle['rule'][] = [
|
||||
'action' => [
|
||||
'type' => 'AbortIncompleteMultipartUpload'
|
||||
],
|
||||
'condition' => $this->formatCondition($condition)
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Object Lifecycle Set Storage Class Rule.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $lifecycle->addSetStorageClassRule('COLDLINE', [
|
||||
* 'age' => 50,
|
||||
* 'isLive' => true
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* // Using customTimeBefore rule with an object's custom time setting.
|
||||
* $lifecycle->addSetStorageClassRule('NEARLINE', [
|
||||
* 'customTimeBefore' => (new \DateTime())->add(
|
||||
* \DateInterval::createFromDateString('+10 days')
|
||||
* )
|
||||
* ]);
|
||||
*
|
||||
* $bucket->update(['lifecycle' => $lifecycle]);
|
||||
*
|
||||
* $object = $bucket->object($objectName);
|
||||
* $object->update([
|
||||
* 'metadata' => [
|
||||
* 'customTime' => '2020-08-17'
|
||||
* ]
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param string $storageClass The target storage class. Values include
|
||||
* `"MULTI_REGIONAL"`, `"REGIONAL"`, `"NEARLINE"`, `"COLDLINE"`,
|
||||
* `"STANDARD"`, and `"DURABLE_REDUCED_AVAILABILITY"`.
|
||||
* @param array $condition {
|
||||
* The condition(s) where the rule will apply.
|
||||
*
|
||||
* @type int $age Age of an object (in days). This condition is
|
||||
* satisfied when an object reaches the specified age.
|
||||
* @type \DateTimeInterface|string $createdBefore This condition is
|
||||
* satisfied when an object is created before midnight of the
|
||||
* specified date in UTC. If a string is given, it must be a date
|
||||
* in RFC 3339 format with only the date part (for instance,
|
||||
* "2013-01-15").
|
||||
* @type \DateTimeInterface|string $customTimeBefore This condition is
|
||||
* satisfied when the custom time on an object is before this date
|
||||
* in UTC. If a string is given, it must be a date in RFC 3339
|
||||
* format with only the date part (for instance, "2013-01-15").
|
||||
* @type int $daysSinceCustomTime Number of days elapsed since the
|
||||
* user-specified timestamp set on an object. The condition is
|
||||
* satisfied if the days elapsed is at least this number. If no
|
||||
* custom timestamp is specified on an object, the condition does
|
||||
* not apply.
|
||||
* @type int $daysSinceNoncurrentTime Number of days elapsed since the
|
||||
* noncurrent timestamp of an object. The condition is satisfied
|
||||
* if the days elapsed is at least this number. This condition is
|
||||
* relevant only for versioned objects. The value of the field
|
||||
* must be a nonnegative integer. If it's zero, the object version
|
||||
* will become eligible for Lifecycle action as soon as it becomes
|
||||
* noncurrent.
|
||||
* @type bool $isLive Relevant only for versioned objects. If the value
|
||||
* is `true`, this condition matches live objects; if the value is
|
||||
* `false`, it matches archived objects.
|
||||
* @type string[] $matchesStorageClass Objects having any of the storage
|
||||
* classes specified by this condition will be matched. Values
|
||||
* include `"MULTI_REGIONAL"`, `"REGIONAL"`, `"NEARLINE"`,
|
||||
* `"ARCHIVE"`, `"COLDLINE"`, `"STANDARD"`, and
|
||||
* `"DURABLE_REDUCED_AVAILABILITY"`.
|
||||
* @type \DateTimeInterface|string $noncurrentTimeBefore This condition
|
||||
* is satisfied when the noncurrent time on an object is before
|
||||
* this timestamp. This condition is relevant only for versioned
|
||||
* objects. If a string is given, it must be a date in RFC 3339
|
||||
* format with only the date part (for instance, "2013-01-15").
|
||||
* @type int $numNewerVersions Relevant only for versioned objects. If
|
||||
* the value is N, this condition is satisfied when there are at
|
||||
* least N versions (including the live version) newer than this
|
||||
* version of the object.
|
||||
* @type string[] $matchesPrefix Objects having names which start with
|
||||
* values specified by this condition will be matched.
|
||||
* @type string[] $matchesSuffix Objects having names which end with
|
||||
* values specified by this condition will be matched.
|
||||
* }
|
||||
* @return Lifecycle
|
||||
*/
|
||||
public function addSetStorageClassRule($storageClass, array $condition)
|
||||
{
|
||||
$this->lifecycle['rule'][] = [
|
||||
'action' => [
|
||||
'type' => 'SetStorageClass',
|
||||
'storageClass' => $storageClass
|
||||
],
|
||||
'condition' => $this->formatCondition($condition)
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all Object Lifecycle rules or rules of a certain action type.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* // Remove all rules.
|
||||
* $lifecycle->clearRules();
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* // Remove all "Delete" based rules.
|
||||
* $lifecycle->clearRules('Delete');
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* // Clear any rules which have an age equal to 50.
|
||||
* $lifecycle->clearRules(function (array $rule) {
|
||||
* return $rule['condition']['age'] === 50
|
||||
* ? false
|
||||
* : true;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param string|callable $action [optional] If a string is provided, it
|
||||
* must be the name of the type of rule to remove (`SetStorageClass`
|
||||
* or `Delete`). All rules of this type will then be cleared. When
|
||||
* providing a callable you may define a custom route for how you
|
||||
* would like to remove rules. The provided callable will be run
|
||||
* through
|
||||
* [array_filter](http://php.net/manual/en/function.array-filter.php).
|
||||
* The callable's argument will be a single lifecycle rule as an
|
||||
* associative array. When returning true from the callable the rule
|
||||
* will be preserved, and if false it will be removed.
|
||||
* **Defaults to** `null`, clearing all assigned rules.
|
||||
* @return Lifecycle
|
||||
* @throws \InvalidArgumentException If a type other than a string or
|
||||
* callabe is provided.
|
||||
*/
|
||||
public function clearRules($action = null)
|
||||
{
|
||||
if (!$action) {
|
||||
$this->lifecycle = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!is_string($action) && !is_callable($action)) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf(
|
||||
'Expected either a string or callable, instead got \'%s\'.',
|
||||
gettype($action)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($this->lifecycle['rule'])) {
|
||||
if (is_string($action)) {
|
||||
$action = function ($rule) use ($action) {
|
||||
return $rule['action']['type'] !== $action;
|
||||
};
|
||||
}
|
||||
|
||||
$this->lifecycle['rule'] = array_filter(
|
||||
$this->lifecycle['rule'],
|
||||
$action
|
||||
);
|
||||
|
||||
if (!$this->lifecycle['rule']) {
|
||||
$this->lifecycle = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
* @return \Generator
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
if (!isset($this->lifecycle['rule'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->lifecycle['rule'] as $rule) {
|
||||
yield $rule;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->lifecycle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
* @param string $offset
|
||||
* @param mixed $value
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->lifecycle['rule'][$offset] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
* @param string $offset
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->lifecycle['rule'][$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
* @param string $offset
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->lifecycle['rule'][$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @access private
|
||||
* @param string $offset
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return isset($this->lifecycle['rule'][$offset])
|
||||
? $this->lifecycle['rule'][$offset]
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply condition-specific formatting rules (such as date formatting) to
|
||||
* conditions.
|
||||
*
|
||||
* @param array $condition
|
||||
* @return array
|
||||
*/
|
||||
private function formatCondition(array $condition)
|
||||
{
|
||||
$rfc339DateFields = [
|
||||
'createdBefore',
|
||||
'customTimeBefore',
|
||||
'noncurrentTimeBefore'
|
||||
];
|
||||
|
||||
foreach ($rfc339DateFields as $field) {
|
||||
if (isset($condition[$field]) && $condition[$field] instanceof \DateTimeInterface) {
|
||||
$condition[$field] = $condition[$field]->format('Y-m-d');
|
||||
}
|
||||
}
|
||||
|
||||
return $condition;
|
||||
}
|
||||
}
|
||||
208
vendor/google/cloud-storage/src/Notification.php
vendored
Normal file
208
vendor/google/cloud-storage/src/Notification.php
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use Google\Cloud\Core\ArrayTrait;
|
||||
use Google\Cloud\Core\Exception\NotFoundException;
|
||||
use Google\Cloud\Storage\Connection\ConnectionInterface;
|
||||
|
||||
/**
|
||||
* Cloud Pub/Sub Notifications sends information about changes to objects in
|
||||
* your buckets to Google Cloud Pub/Sub, where the information is added to a
|
||||
* Cloud Pub/Sub topic of your choice in the form of messages. For example,
|
||||
* you can track objects that are created and deleted in your bucket. Each
|
||||
* notification contains information describing both the event that triggered it
|
||||
* and the object that changed.
|
||||
*
|
||||
* To utilize this class and see more examples, please see the relevant
|
||||
* notifications based methods exposed on {@see Bucket}.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* use Google\Cloud\Storage\StorageClient;
|
||||
*
|
||||
* $storage = new StorageClient();
|
||||
*
|
||||
* // Fetch an existing notification by ID.
|
||||
* $bucket = $storage->bucket('my-bucket');
|
||||
* $notification = $bucket->notification('2482');
|
||||
* ```
|
||||
*
|
||||
* @see https://cloud.google.com/storage/docs/pubsub-notifications
|
||||
* @experimental The experimental flag means that while we believe this method
|
||||
* or class is ready for use, it may change before release in backwards-
|
||||
* incompatible ways. Please use with caution, and test thoroughly when
|
||||
* upgrading.
|
||||
*/
|
||||
class Notification
|
||||
{
|
||||
use ArrayTrait;
|
||||
|
||||
/**
|
||||
* @var ConnectionInterface Represents a connection to Cloud Storage.
|
||||
* @internal
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* @var array The notification's identity.
|
||||
*/
|
||||
private $identity;
|
||||
|
||||
/**
|
||||
* @var array The notification's metadata.
|
||||
*/
|
||||
private $info;
|
||||
|
||||
/**
|
||||
* @param ConnectionInterface $connection Represents a connection to Cloud
|
||||
* Storage. This object is created by StorageClient,
|
||||
* and should not be instantiated outside of this client.
|
||||
* @param string $id The notification's ID.
|
||||
* @param string $bucket The name of the bucket associated with this
|
||||
* notification.
|
||||
* @param array $info [optional] The notification's metadata.
|
||||
*/
|
||||
public function __construct(ConnectionInterface $connection, $id, $bucket, array $info = [])
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->identity = [
|
||||
'bucket' => $bucket,
|
||||
'notification' => $id,
|
||||
'userProject' => $this->pluck('requesterProjectId', $info, false)
|
||||
];
|
||||
$this->info = $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether or not the notification exists.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* if ($notification->exists()) {
|
||||
* echo 'Notification exists!';
|
||||
* }
|
||||
* ```
|
||||
* @param array $options [optional] {
|
||||
* Configuration options.
|
||||
* }
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(array $options = [])
|
||||
{
|
||||
try {
|
||||
$this->connection->getNotification($options + $this->identity + ['fields' => 'id']);
|
||||
} catch (NotFoundException $ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the notification.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $notification->delete();
|
||||
* ```
|
||||
*
|
||||
* @codingStandardsIgnoreStart
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/notifications/delete Notifications delete API documentation.
|
||||
* @codingStandardsIgnoreEnd
|
||||
*
|
||||
* @param array $options [optional]
|
||||
* @return void
|
||||
*/
|
||||
public function delete(array $options = [])
|
||||
{
|
||||
$this->connection->deleteNotification($options + $this->identity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the notification's details. If no notification data is cached a
|
||||
* network request will be made to retrieve it.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $info = $notification->info();
|
||||
* echo $info['topic'];
|
||||
* ```
|
||||
*
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/notifications/get Notifications get API documentation.
|
||||
*
|
||||
* @param array $options [optional]
|
||||
* @return array
|
||||
*/
|
||||
public function info(array $options = [])
|
||||
{
|
||||
return $this->info ?: $this->reload($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a network request to reload the notification's details.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $notification->reload();
|
||||
* $info = $notification->info();
|
||||
* echo $info['topic'];
|
||||
* ```
|
||||
*
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/notifications/get Notifications get API documentation.
|
||||
*
|
||||
* @param array $options [optional]
|
||||
* @return array
|
||||
*/
|
||||
public function reload(array $options = [])
|
||||
{
|
||||
return $this->info = $this->connection->getNotification(
|
||||
$options + $this->identity
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the notification's ID.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* echo $notification->id();
|
||||
* ```
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id()
|
||||
{
|
||||
return $this->identity['notification'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the notification's identity.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* echo $notification->identity()['bucket'];
|
||||
* ```
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function identity()
|
||||
{
|
||||
return $this->identity;
|
||||
}
|
||||
}
|
||||
45
vendor/google/cloud-storage/src/ObjectIterator.php
vendored
Normal file
45
vendor/google/cloud-storage/src/ObjectIterator.php
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use Google\Cloud\Core\Iterator\ItemIteratorTrait;
|
||||
|
||||
/**
|
||||
* ObjectIterator
|
||||
*
|
||||
* Iterates over a set of {@see StorageObject} items.
|
||||
*
|
||||
* @implements \Iterator<int, StorageObject>
|
||||
*/
|
||||
class ObjectIterator implements \Iterator
|
||||
{
|
||||
use ItemIteratorTrait;
|
||||
|
||||
/**
|
||||
* Gets a list of prefixes of objects matching-but-not-listed up to and
|
||||
* including the requested delimiter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function prefixes()
|
||||
{
|
||||
return method_exists($this->pageIterator, 'prefixes')
|
||||
? $this->pageIterator->prefixes()
|
||||
: [];
|
||||
}
|
||||
}
|
||||
80
vendor/google/cloud-storage/src/ObjectPageIterator.php
vendored
Normal file
80
vendor/google/cloud-storage/src/ObjectPageIterator.php
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use Google\Cloud\Core\Iterator\PageIteratorTrait;
|
||||
|
||||
/**
|
||||
* ObjectPageIterator
|
||||
*
|
||||
* Iterates over a set of pages containing
|
||||
* {@see StorageObject} items.
|
||||
*/
|
||||
class ObjectPageIterator implements \Iterator
|
||||
{
|
||||
use PageIteratorTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $prefixes = [];
|
||||
|
||||
/**
|
||||
* Gets a list of prefixes of objects matching-but-not-listed up to and
|
||||
* including the requested delimiter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function prefixes()
|
||||
{
|
||||
return $this->prefixes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current page.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
if (!$this->page) {
|
||||
$this->page = $this->executeCall();
|
||||
}
|
||||
|
||||
if (isset($this->page['prefixes'])) {
|
||||
$this->updatePrefixes();
|
||||
}
|
||||
|
||||
return $this->get($this->itemsPath, $this->page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new prefixes to the list.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function updatePrefixes()
|
||||
{
|
||||
foreach ($this->page['prefixes'] as $prefix) {
|
||||
if (!in_array($prefix, $this->prefixes)) {
|
||||
$this->prefixes[] = $prefix;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
vendor/google/cloud-storage/src/ReadStream.php
vendored
Normal file
93
vendor/google/cloud-storage/src/ReadStream.php
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use GuzzleHttp\Psr7\StreamDecoratorTrait;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* A Stream implementation that wraps a GuzzleHttp download stream to
|
||||
* provide `getSize()` from the response headers.
|
||||
*/
|
||||
class ReadStream implements StreamInterface
|
||||
{
|
||||
use StreamDecoratorTrait;
|
||||
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* Create a new ReadStream.
|
||||
*
|
||||
* @param StreamInterface $stream The stream interface to wrap
|
||||
*/
|
||||
public function __construct(StreamInterface $stream)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full size of the buffer. If the underlying stream does
|
||||
* not report it's size, try to fetch the size from the Content-Length
|
||||
* response header.
|
||||
*
|
||||
* @return int The size of the stream.
|
||||
*/
|
||||
public function getSize(): ?int
|
||||
{
|
||||
return $this->stream->getSize() ?: $this->getSizeFromMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to fetch the size from the Content-Length response header.
|
||||
* If we cannot, return 0.
|
||||
*
|
||||
* @return int The Size of the stream
|
||||
*/
|
||||
private function getSizeFromMetadata(): int
|
||||
{
|
||||
foreach ($this->stream->getMetadata('wrapper_data') as $value) {
|
||||
if (substr($value, 0, 15) == 'Content-Length:') {
|
||||
return (int) substr($value, 16);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes from the underlying buffer, retrying until we have read
|
||||
* enough bytes or we cannot read any more. We do this because the
|
||||
* internal C code for filling a buffer does not account for when
|
||||
* we try to read large chunks from a user-land stream that does not
|
||||
* return enough bytes.
|
||||
*
|
||||
* @param int $length The number of bytes to read.
|
||||
* @return string Read bytes from the underlying stream.
|
||||
*/
|
||||
public function read($length): string
|
||||
{
|
||||
$data = '';
|
||||
do {
|
||||
$moreData = $this->stream->read($length);
|
||||
$data .= $moreData;
|
||||
$readLength = strlen($moreData);
|
||||
$length -= $readLength;
|
||||
} while ($length > 0 && $readLength > 0);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
925
vendor/google/cloud-storage/src/SigningHelper.php
vendored
Normal file
925
vendor/google/cloud-storage/src/SigningHelper.php
vendored
Normal file
@@ -0,0 +1,925 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use Google\Auth\CredentialsLoader;
|
||||
use Google\Auth\SignBlobInterface;
|
||||
use Google\Cloud\Core\ArrayTrait;
|
||||
use Google\Cloud\Core\Exception\ServiceException;
|
||||
use Google\Cloud\Core\JsonTrait;
|
||||
use Google\Cloud\Core\Timestamp;
|
||||
use Google\Cloud\Storage\Connection\ConnectionInterface;
|
||||
use Google\Cloud\Storage\Connection\RetryTrait;
|
||||
|
||||
/**
|
||||
* Provides common methods for signing storage URLs.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class SigningHelper
|
||||
{
|
||||
use ArrayTrait;
|
||||
use JsonTrait;
|
||||
use RetryTrait;
|
||||
|
||||
const DEFAULT_URL_SIGNING_VERSION = 'v2';
|
||||
const DEFAULT_DOWNLOAD_HOST = 'storage.googleapis.com';
|
||||
|
||||
const V4_ALGO_NAME = 'GOOG4-RSA-SHA256';
|
||||
const V4_TIMESTAMP_FORMAT = 'Ymd\THis\Z';
|
||||
const V4_DATESTAMP_FORMAT = 'Ymd';
|
||||
const MAX_RETRIES = 5;
|
||||
|
||||
/**
|
||||
* Create or fetch a SigningHelper instance.
|
||||
*
|
||||
* @return SigningHelper
|
||||
*/
|
||||
public static function getHelper()
|
||||
{
|
||||
static $helper;
|
||||
if (!$helper) {
|
||||
$helper = new static();
|
||||
}
|
||||
|
||||
return $helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign using the version inferred from `$options.version`.
|
||||
*
|
||||
* @param ConnectionInterface $connection A connection to the Cloud Storage
|
||||
* API. This object is created by StorageClient,
|
||||
* and should not be instantiated outside of this client.
|
||||
* @param Timestamp|\DateTimeInterface|int $expires The signed URL
|
||||
* expiration.
|
||||
* @param string $resource The URI to the storage resource, preceded by a
|
||||
* leading slash.
|
||||
* @param int|null $generation The resource generation.
|
||||
* @param array $options Configuration options. See
|
||||
* {@see StorageObject::signedUrl()} for
|
||||
* details.
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException If required data could not be gathered from
|
||||
* credentials.
|
||||
* @throws \RuntimeException If OpenSSL signing is required by user input
|
||||
* and OpenSSL is not available.
|
||||
*/
|
||||
public function sign(ConnectionInterface $connection, $expires, $resource, $generation, array $options)
|
||||
{
|
||||
$version = $options['version'] ?? self::DEFAULT_URL_SIGNING_VERSION;
|
||||
|
||||
unset($options['version']);
|
||||
|
||||
switch (strtolower($version)) {
|
||||
case 'v2':
|
||||
$method = 'v2Sign';
|
||||
break;
|
||||
|
||||
case 'v4':
|
||||
$method = 'v4Sign';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid signing version.');
|
||||
}
|
||||
|
||||
return call_user_func_array([$this, $method], [
|
||||
$connection,
|
||||
$expires,
|
||||
$resource,
|
||||
$generation,
|
||||
$options
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a URL using Google Signed URLs v2.
|
||||
*
|
||||
* This method will be deprecated in the future.
|
||||
*
|
||||
* @param ConnectionInterface $connection A connection to the Cloud Storage
|
||||
* API. This object is created by StorageClient,
|
||||
* and should not be instantiated outside of this client.
|
||||
* @param Timestamp|\DateTimeInterface|int $expires The signed URL
|
||||
* expiration.
|
||||
* @param string $resource The URI to the storage resource, preceded by a
|
||||
* leading slash.
|
||||
* @param int|null $generation The resource generation.
|
||||
* @param array $options Configuration options. See
|
||||
* {@see StorageObject::signedUrl()} for
|
||||
* details.
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException If required data could not be gathered from
|
||||
* credentials.
|
||||
* @throws \RuntimeException If OpenSSL signing is required by user input
|
||||
* and OpenSSL is not available.
|
||||
*/
|
||||
public function v2Sign(ConnectionInterface $connection, $expires, $resource, $generation, array $options)
|
||||
{
|
||||
list($credentials, $options) = $this->getSigningCredentials($connection, $options);
|
||||
|
||||
$expires = $this->normalizeExpiration($expires);
|
||||
list($resource, $bucket) = $this->normalizeResource($resource);
|
||||
$options = $this->normalizeOptions($options);
|
||||
$headers = $this->normalizeHeaders($options['headers']);
|
||||
|
||||
if ($options['virtualHostedStyle']) {
|
||||
$options['bucketBoundHostname'] = sprintf(
|
||||
'%s.storage.googleapis.com',
|
||||
$bucket
|
||||
);
|
||||
}
|
||||
|
||||
// Make sure disallowed headers are not included.
|
||||
$illegalHeaders = [
|
||||
'x-goog-encryption-key',
|
||||
'x-goog-encryption-key-sha256'
|
||||
];
|
||||
if ($illegal = array_intersect_key(array_flip($illegalHeaders), $headers)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'%s %s not allowed in Signed URL headers.',
|
||||
implode(' and ', array_keys($illegal)),
|
||||
count($illegal) === 1 ? 'is' : 'are'
|
||||
));
|
||||
}
|
||||
|
||||
// Sort headers by name.
|
||||
ksort($headers);
|
||||
|
||||
$toSign = [
|
||||
$options['method'],
|
||||
$options['contentMd5'],
|
||||
$options['contentType'],
|
||||
$expires,
|
||||
];
|
||||
|
||||
$signedHeaders = [];
|
||||
foreach ($headers as $name => $value) {
|
||||
$signedHeaders[] = $name . ':' . $value;
|
||||
}
|
||||
|
||||
// Push the headers onto the end of the signing string.
|
||||
if ($signedHeaders) {
|
||||
$toSign = array_merge($toSign, $signedHeaders);
|
||||
}
|
||||
|
||||
$toSign[] = $resource;
|
||||
|
||||
$stringToSign = $this->createV2CanonicalRequest($toSign);
|
||||
|
||||
// Use exponential backOff
|
||||
$signature = $this->retrySignBlob(fn () => $credentials->signBlob($stringToSign, [
|
||||
'forceOpenssl' => $options['forceOpenssl']
|
||||
]));
|
||||
|
||||
// Start with user-provided query params and add required parameters.
|
||||
$params = $options['queryParams'];
|
||||
$params['GoogleAccessId'] = $credentials->getClientName();
|
||||
$params['Expires'] = $expires;
|
||||
$params['Signature'] = $signature;
|
||||
|
||||
// urlencode parameter values
|
||||
foreach ($params as &$value) {
|
||||
$value = rawurlencode($value ?? '');
|
||||
}
|
||||
|
||||
$params = $this->addCommonParams($generation, $params, $options);
|
||||
|
||||
$queryString = $this->buildQueryString($params);
|
||||
|
||||
$resource = $this->normalizeUriPath($options['bucketBoundHostname'], $resource);
|
||||
return 'https://' . $options['bucketBoundHostname'] . $resource . '?' . $queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a storage URL using Google Signed URLs v4.
|
||||
*
|
||||
* @param ConnectionInterface $connection A connection to the Cloud Storage
|
||||
* API. This object is created by StorageClient,
|
||||
* and should not be instantiated outside of this client.
|
||||
* @param Timestamp|\DateTimeInterface|int $expires The signed URL
|
||||
* expiration.
|
||||
* @param string $resource The URI to the storage resource, preceded by a
|
||||
* leading slash.
|
||||
* @param int|null $generation The resource generation.
|
||||
* @param array $options Configuration options. See
|
||||
* {@see StorageObject::signedUrl()} for
|
||||
* details.
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException If required data could not be gathered from
|
||||
* credentials.
|
||||
* @throws \RuntimeException If OpenSSL signing is required by user input
|
||||
* and OpenSSL is not available.
|
||||
*/
|
||||
public function v4Sign(ConnectionInterface $connection, $expires, $resource, $generation, array $options)
|
||||
{
|
||||
list($credentials, $options) = $this->getSigningCredentials($connection, $options);
|
||||
|
||||
$expires = $this->normalizeExpiration($expires);
|
||||
list($resource, $bucket) = $this->normalizeResource($resource);
|
||||
$options = $this->normalizeOptions($options);
|
||||
|
||||
$time = $options['timestamp'];
|
||||
$requestTimestamp = $time->format(self::V4_TIMESTAMP_FORMAT);
|
||||
$requestDatestamp = $time->format(self::V4_DATESTAMP_FORMAT);
|
||||
$timeSeconds = $time->format('U');
|
||||
|
||||
$expireLimit = $timeSeconds + 604800;
|
||||
if ($expires > $expireLimit) {
|
||||
throw new \InvalidArgumentException(
|
||||
'V4 Signed URLs may not have an expiration greater than seven days in the future.'
|
||||
);
|
||||
}
|
||||
|
||||
$clientEmail = $credentials->getClientName();
|
||||
$credentialScope = sprintf('%s/auto/storage/goog4_request', $requestDatestamp);
|
||||
$credential = sprintf('%s/%s', $clientEmail, $credentialScope);
|
||||
|
||||
if ($options['virtualHostedStyle']) {
|
||||
$options['bucketBoundHostname'] = sprintf(
|
||||
'%s.storage.googleapis.com',
|
||||
$bucket
|
||||
);
|
||||
}
|
||||
|
||||
// Add headers and query params based on provided options.
|
||||
$params = $options['queryParams'];
|
||||
$headers = $options['headers'] + [
|
||||
'host' => $options['bucketBoundHostname']
|
||||
];
|
||||
|
||||
if ($options['contentType']) {
|
||||
$headers['content-type'] = $options['contentType'];
|
||||
}
|
||||
|
||||
if ($options['contentMd5']) {
|
||||
$headers['content-md5'] = $options['contentMd5'];
|
||||
}
|
||||
|
||||
$params = $this->addCommonParams($generation, $params, $options);
|
||||
|
||||
$headers = $this->normalizeHeaders($headers);
|
||||
|
||||
// sort headers by name
|
||||
ksort($headers, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
|
||||
// Canonical headers are a list, newline separated, of keys and values,
|
||||
// comma separated.
|
||||
// Signed headers are a list of keys, separated by a semicolon.
|
||||
$canonicalHeaders = [];
|
||||
$signedHeaders = [];
|
||||
foreach ($headers as $key => $val) {
|
||||
$canonicalHeaders[] = sprintf('%s:%s', $key, $val);
|
||||
$signedHeaders[] = $key;
|
||||
}
|
||||
$canonicalHeaders = implode("\n", $canonicalHeaders) . "\n";
|
||||
|
||||
$signedHeaders = implode(';', $signedHeaders);
|
||||
|
||||
// Add required query parameters.
|
||||
$params = [
|
||||
'X-Goog-Algorithm' => self::V4_ALGO_NAME,
|
||||
'X-Goog-Credential' => $credential,
|
||||
'X-Goog-Date' => $requestTimestamp,
|
||||
'X-Goog-Expires' => $expires - $timeSeconds,
|
||||
'X-Goog-SignedHeaders' => $signedHeaders,
|
||||
] + $params;
|
||||
|
||||
$paramNames = [];
|
||||
foreach ($params as $key => $val) {
|
||||
$paramNames[] = $key;
|
||||
}
|
||||
|
||||
sort($paramNames, SORT_REGULAR);
|
||||
|
||||
$sortedParams = [];
|
||||
foreach ($paramNames as $name) {
|
||||
$sortedParams[rawurlencode($name)] = rawurlencode($params[$name]);
|
||||
}
|
||||
|
||||
$canonicalQueryString = $this->buildQueryString($sortedParams);
|
||||
$canonicalResource = $this->normalizeCanonicalRequestResource(
|
||||
$resource,
|
||||
$options['bucketBoundHostname'],
|
||||
$options['virtualHostedStyle']
|
||||
);
|
||||
|
||||
$canonicalRequest = [
|
||||
$options['method'],
|
||||
$canonicalResource,
|
||||
$canonicalQueryString,
|
||||
$canonicalHeaders,
|
||||
$signedHeaders,
|
||||
$this->getPayloadHash($headers)
|
||||
];
|
||||
|
||||
$requestHash = $this->createV4CanonicalRequest($canonicalRequest);
|
||||
|
||||
// Construct the string to sign.
|
||||
$stringToSign = implode("\n", [
|
||||
self::V4_ALGO_NAME,
|
||||
$requestTimestamp,
|
||||
$credentialScope,
|
||||
$requestHash
|
||||
]);
|
||||
|
||||
$signature = bin2hex(base64_decode($this->retrySignBlob(
|
||||
fn () => $credentials->signBlob($stringToSign, [
|
||||
'forceOpenssl' => $options['forceOpenssl']
|
||||
])
|
||||
) ?? ''));
|
||||
|
||||
// Construct the modified resource name. If a custom hostname is provided,
|
||||
// this will remove the bucket name from the resource.
|
||||
$resource = $this->normalizeUriPath($options['bucketBoundHostname'], $resource);
|
||||
|
||||
$scheme = $this->chooseScheme(
|
||||
$options['scheme'],
|
||||
$options['bucketBoundHostname'],
|
||||
$options['virtualHostedStyle']
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'%s://%s%s?%s&X-Goog-Signature=%s',
|
||||
$scheme,
|
||||
$options['bucketBoundHostname'],
|
||||
$resource,
|
||||
$canonicalQueryString,
|
||||
$signature
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTTP POST policy using v4 signing.
|
||||
*
|
||||
* @param ConnectionInterface $connection A Connection to Google Cloud Storage.
|
||||
* This object is created by StorageClient,
|
||||
* and should not be instantiated outside of this client.
|
||||
* @param Timestamp|\DateTimeInterface|int $expires The signed URL
|
||||
* expiration.
|
||||
* @param string $resource The URI to the storage resource, preceded by a
|
||||
* leading slash.
|
||||
* @param array $options Configuration options. See
|
||||
* {@see Bucket::generateSignedPostPolicyV4()} for details.
|
||||
* @return array An associative array, containing (string) `uri` and
|
||||
* (array) `fields` keys.
|
||||
*/
|
||||
public function v4PostPolicy(
|
||||
ConnectionInterface $connection,
|
||||
$expires,
|
||||
$resource,
|
||||
array $options = []
|
||||
) {
|
||||
list($credentials, $options) = $this->getSigningCredentials($connection, $options);
|
||||
|
||||
$expires = $this->normalizeExpiration($expires);
|
||||
list($resource, $bucket, $object) = $this->normalizeResource($resource, false);
|
||||
$object = trim($object, '/');
|
||||
|
||||
$options = $this->normalizeOptions($options) + [
|
||||
'fields' => [],
|
||||
'conditions' => [],
|
||||
'successActionRedirect' => null,
|
||||
'successActionStatus' => null
|
||||
];
|
||||
|
||||
$time = $options['timestamp'];
|
||||
$requestTimestamp = $time->format(self::V4_TIMESTAMP_FORMAT);
|
||||
$requestDatestamp = $time->format(self::V4_DATESTAMP_FORMAT);
|
||||
$expiration = \DateTimeImmutable::createFromFormat('U', (string) $expires);
|
||||
$expirationTimestamp = str_replace(
|
||||
'+00:00',
|
||||
'Z',
|
||||
$expiration->format(\DateTime::RFC3339)
|
||||
);
|
||||
|
||||
$clientEmail = $credentials->getClientName();
|
||||
$credentialScope = sprintf('%s/auto/storage/goog4_request', $requestDatestamp);
|
||||
$credential = sprintf('%s/%s', $clientEmail, $credentialScope);
|
||||
|
||||
if ($options['virtualHostedStyle']) {
|
||||
$options['bucketBoundHostname'] = sprintf(
|
||||
'%s.storage.googleapis.com',
|
||||
$bucket
|
||||
);
|
||||
}
|
||||
|
||||
$fields = array_merge($options['fields'], [
|
||||
'key' => $object,
|
||||
'x-goog-algorithm' => self::V4_ALGO_NAME,
|
||||
'x-goog-credential' => $credential,
|
||||
'x-goog-date' => $requestTimestamp
|
||||
]);
|
||||
|
||||
$conditions = $options['conditions'];
|
||||
foreach ($options['fields'] as $key => $value) {
|
||||
$conditions[] = [$key => $value];
|
||||
}
|
||||
|
||||
foreach ($conditions as $key => $value) {
|
||||
$key = $key;
|
||||
$value = $value;
|
||||
$conditions[$key] = $value;
|
||||
}
|
||||
|
||||
$conditions = array_merge($conditions, [
|
||||
['bucket' => $bucket],
|
||||
['key' => $object],
|
||||
['x-goog-date' => $requestTimestamp],
|
||||
['x-goog-credential' => $credential],
|
||||
['x-goog-algorithm' => self::V4_ALGO_NAME],
|
||||
]);
|
||||
|
||||
$policy = [
|
||||
'conditions' => $conditions,
|
||||
'expiration' => $expirationTimestamp
|
||||
];
|
||||
|
||||
$json = str_replace('\\\u', '\\u', json_encode($policy, JSON_UNESCAPED_SLASHES));
|
||||
$stringToSign = base64_encode($json);
|
||||
|
||||
$signature = bin2hex(base64_decode($credentials->signBlob($stringToSign, [
|
||||
'forceOpenssl' => $options['forceOpenssl']
|
||||
])));
|
||||
|
||||
$fields['x-goog-signature'] = $signature;
|
||||
$fields['policy'] = $stringToSign;
|
||||
|
||||
// Construct the modified resource name. If a custom hostname is provided,
|
||||
// this will remove the bucket name from the resource.
|
||||
$resource = $this->normalizeUriPath($options['bucketBoundHostname'], '/' . $bucket, true);
|
||||
|
||||
$scheme = $this->chooseScheme(
|
||||
$options['scheme'],
|
||||
$options['bucketBoundHostname'],
|
||||
$options['virtualHostedStyle']
|
||||
);
|
||||
|
||||
return [
|
||||
'url' => sprintf(
|
||||
'%s://%s%s',
|
||||
$scheme,
|
||||
$options['bucketBoundHostname'],
|
||||
$resource
|
||||
),
|
||||
'fields' => $fields
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a canonical request hash for a V4 Signed URL.
|
||||
*
|
||||
* NOTE: While in most cases `PHP_EOL` is preferable to a system-specific
|
||||
* character, in this case `\n` is required.
|
||||
*
|
||||
* @param array $canonicalRequest The canonical request, with each element
|
||||
* representing a line in the request.
|
||||
* @return string
|
||||
*/
|
||||
private function createV4CanonicalRequest(array $canonicalRequest)
|
||||
{
|
||||
$canonicalRequestString = implode("\n", $canonicalRequest);
|
||||
return bin2hex(hash('sha256', $canonicalRequestString, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a canonical request for a V2 Signed URL.
|
||||
*
|
||||
* NOTE: While in most cases `PHP_EOL` is preferable to a system-specific
|
||||
* character, in this case `\n` is required.
|
||||
*
|
||||
* @param array $canonicalRequest The canonical request, with each element
|
||||
* representing a line in the request.
|
||||
* @return string
|
||||
*/
|
||||
private function createV2CanonicalRequest(array $canonicalRequest)
|
||||
{
|
||||
return implode("\n", $canonicalRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the correct URL scheme.
|
||||
*
|
||||
* @param string $scheme The scheme provided by the user or defaults.
|
||||
* @param string $bucketBoundHostname The bucketBoundHostname provided by the user or defaults.
|
||||
* @param bool $virtualHostedStyle Whether virtual host style is enabled.
|
||||
* @return string
|
||||
*/
|
||||
private function chooseScheme($scheme, $bucketBoundHostname, $virtualHostedStyle = false)
|
||||
{
|
||||
// bucketBoundHostname not used -- always https.
|
||||
if ($bucketBoundHostname === self::DEFAULT_DOWNLOAD_HOST) {
|
||||
return 'https';
|
||||
}
|
||||
|
||||
// virtualHostedStyle enabled -- always https.
|
||||
if ($virtualHostedStyle) {
|
||||
return 'https';
|
||||
}
|
||||
|
||||
// not virtual hosted style, and custom hostname -- use default (http) or user choice.
|
||||
return $scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `X-Goog-Content-SHA256` header is provided, use that as the payload.
|
||||
* Otherwise, `UNSIGNED-PAYLOAD`.
|
||||
*
|
||||
* @param array $headers
|
||||
* @return string
|
||||
*/
|
||||
private function getPayloadHash(array $headers)
|
||||
{
|
||||
if (!isset($headers['x-goog-content-sha256'])) {
|
||||
return 'UNSIGNED-PAYLOAD';
|
||||
}
|
||||
|
||||
return $headers['x-goog-content-sha256'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes and validates an expiration.
|
||||
*
|
||||
* @param Timestamp|\DateTimeInterface|int $expires The expiration
|
||||
* @return int
|
||||
* @throws \InvalidArgumentException If an invalid value is given.
|
||||
*/
|
||||
private function normalizeExpiration($expires)
|
||||
{
|
||||
if ($expires instanceof Timestamp) {
|
||||
$seconds = $expires->get()->format('U');
|
||||
} elseif ($expires instanceof \DateTimeInterface) {
|
||||
$seconds = $expires->format('U');
|
||||
} elseif (is_numeric($expires)) {
|
||||
$seconds = (int) $expires;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid expiration.');
|
||||
}
|
||||
|
||||
return $seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes and encodes the resource identifier.
|
||||
*
|
||||
* @param string $resource The resource identifier. In form
|
||||
* `[/]$bucket/$object`.
|
||||
* @return array A list, where index 0 is the resource path, with pieces
|
||||
* encoded and prefixed with a forward slash, index 1 is the bucket
|
||||
* name, and index 2 is the object name, relative to the bucket.
|
||||
*/
|
||||
private function normalizeResource($resource, $urlencode = true)
|
||||
{
|
||||
$pieces = explode('/', trim($resource, '/'));
|
||||
|
||||
if ($urlencode) {
|
||||
array_walk($pieces, function (&$piece) {
|
||||
$piece = rawurlencode($piece);
|
||||
});
|
||||
}
|
||||
|
||||
$bucket = $pieces[0];
|
||||
|
||||
$relative = $pieces;
|
||||
array_shift($relative);
|
||||
|
||||
return [
|
||||
'/' . implode('/', $pieces),
|
||||
$bucket,
|
||||
'/' . implode('/', $relative),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes the user input options, filters and validates data.
|
||||
*
|
||||
* @param array $options Signed URL configuration options.
|
||||
* @return array
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function normalizeOptions(array $options)
|
||||
{
|
||||
$options += [
|
||||
'allowPost' => false,
|
||||
'cname' => null, //@deprecated
|
||||
'bucketBoundHostname' => self::DEFAULT_DOWNLOAD_HOST,
|
||||
'contentMd5' => null,
|
||||
'contentType' => null,
|
||||
'forceOpenssl' => false,
|
||||
'headers' => [],
|
||||
'keyFile' => null,
|
||||
'keyFilePath' => null,
|
||||
'credentialsFetcher' => null,
|
||||
'method' => 'GET',
|
||||
'queryParams' => [],
|
||||
'responseDisposition' => null,
|
||||
'responseType' => null,
|
||||
'saveAsName' => null,
|
||||
|
||||
// note that in almost every case this default will be overridden.
|
||||
'scheme' => 'http',
|
||||
'timestamp' => null,
|
||||
'virtualHostedStyle' => false,
|
||||
];
|
||||
|
||||
$allowedMethods = ['GET', 'PUT', 'POST', 'DELETE'];
|
||||
$options['method'] = strtoupper($options['method']);
|
||||
if (!in_array($options['method'], $allowedMethods)) {
|
||||
throw new \InvalidArgumentException('$options.method must be one of `GET`, `PUT` or `DELETE`.');
|
||||
}
|
||||
|
||||
if ($options['method'] === 'POST' && !$options['allowPost']) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid method. To create an upload URI, use StorageObject::signedUploadUrl().'
|
||||
);
|
||||
}
|
||||
|
||||
// Rewrite deprecated `cname` to new `bucketBoundHostname`.
|
||||
if ($options['cname'] && $options['bucketBoundHostname'] === self::DEFAULT_DOWNLOAD_HOST) {
|
||||
$options['bucketBoundHostname'] = $options['cname'];
|
||||
}
|
||||
|
||||
// strip protocol from hostname.
|
||||
$hostnameParts = explode('//', $options['bucketBoundHostname']);
|
||||
if (count($hostnameParts) > 1) {
|
||||
$options['bucketBoundHostname'] = $hostnameParts[1];
|
||||
}
|
||||
|
||||
$options['bucketBoundHostname'] = trim($options['bucketBoundHostname'], '/');
|
||||
|
||||
// If a timestamp is provided, use it in place of `now` for v4 URLs only..
|
||||
// This option exists for testing purposes, and should not generally be provided by users.
|
||||
if ($options['timestamp']) {
|
||||
if (!($options['timestamp'] instanceof \DateTimeInterface)) {
|
||||
if (!is_string($options['timestamp'])) {
|
||||
throw new \InvalidArgumentException(
|
||||
'User-provided timestamps must be a string or instance of `\DateTimeInterface`.'
|
||||
);
|
||||
}
|
||||
|
||||
$options['timestamp'] = \DateTimeImmutable::createFromFormat(
|
||||
\DateTime::RFC3339,
|
||||
$options['timestamp'],
|
||||
new \DateTimeZone('UTC')
|
||||
);
|
||||
|
||||
if (!$options['timestamp']) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Given timestamp string is in an invalid format. Provide timestamp formatted as follows: `' .
|
||||
\DateTime::RFC3339 .
|
||||
'`. Note that timestamps MUST be in UTC.'
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$options['timestamp'] = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
|
||||
}
|
||||
|
||||
unset(
|
||||
$options['cname'],
|
||||
$options['allowPost']
|
||||
);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans and normalizes header values.
|
||||
*
|
||||
* Arrays of values are collapsed into a comma-separated list, trailing and
|
||||
* leading spaces are removed, newlines are replaced by empty strings, and
|
||||
* multiple whitespace chars are replaced by a single space.
|
||||
*
|
||||
* @param array $headers Input headers
|
||||
* @return array
|
||||
*/
|
||||
private function normalizeHeaders(array $headers)
|
||||
{
|
||||
$out = [];
|
||||
foreach ($headers as $name => $value) {
|
||||
$name = strtolower(trim($name));
|
||||
// collapse arrays of values into a comma-separated list.
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
|
||||
foreach ($value as &$headerValue) {
|
||||
// strip trailing and leading spaces.
|
||||
$headerValue = trim($headerValue);
|
||||
|
||||
// replace newlines with empty strings.
|
||||
$headerValue = str_replace(PHP_EOL, '', $headerValue);
|
||||
|
||||
// collapse multiple whitespace chars to a single space.
|
||||
$headerValue = preg_replace('/[\s]+/', ' ', $headerValue);
|
||||
}
|
||||
|
||||
$out[$name] = implode(', ', $value);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a resource formatted for use in a URI.
|
||||
*
|
||||
* If the bucketBoundHostname is other than the default, will omit the bucket name.
|
||||
*
|
||||
* @param string $bucketBoundHostname The bucketBoundHostname provided by the user, or the default
|
||||
* value.
|
||||
* @param string $resource The GCS resource path (i.e. /bucket/object).
|
||||
* @return string
|
||||
*/
|
||||
private function normalizeUriPath($bucketBoundHostname, $resource, $withTrailingSlash = false)
|
||||
{
|
||||
if ($bucketBoundHostname !== self::DEFAULT_DOWNLOAD_HOST) {
|
||||
$resourceParts = explode('/', trim($resource, '/'));
|
||||
array_shift($resourceParts);
|
||||
|
||||
// Resource is a Bucket.
|
||||
if (empty($resourceParts)) {
|
||||
$resource = '/';
|
||||
} else {
|
||||
$resource = '/' . implode('/', $resourceParts);
|
||||
}
|
||||
}
|
||||
|
||||
$resource = rtrim($resource, '/');
|
||||
|
||||
return $withTrailingSlash
|
||||
? $resource . '/'
|
||||
: $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the resource provided to the canonical request string.
|
||||
*
|
||||
* @param string $resource
|
||||
* @param string $bucketBoundHostname
|
||||
* @param boolean $virtualHostedStyle
|
||||
* @return string
|
||||
*/
|
||||
private function normalizeCanonicalRequestResource($resource, $bucketBoundHostname, $virtualHostedStyle = false)
|
||||
{
|
||||
if ($bucketBoundHostname === self::DEFAULT_DOWNLOAD_HOST && !$virtualHostedStyle) {
|
||||
return $resource;
|
||||
}
|
||||
|
||||
$pieces = explode('/', trim($resource, '/'));
|
||||
array_shift($pieces);
|
||||
return '/' . implode('/', $pieces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the credentials for use with signing.
|
||||
*
|
||||
* @param ConnectionInterface $connection A Storage connection object.
|
||||
* This object is created by StorageClient,
|
||||
* and should not be instantiated outside of this client.
|
||||
* @param array $options Configuration options.
|
||||
* @return array A list containing a credentials object at index 0 and the
|
||||
* modified options at index 1.
|
||||
* @throws \RuntimeException If the credentials type is not valid for signing.
|
||||
* @throws \InvalidArgumentException If a keyfile is given and is not valid.
|
||||
*/
|
||||
private function getSigningCredentials(ConnectionInterface $connection, array $options)
|
||||
{
|
||||
$keyFilePath = $options['keyFilePath'] ?? null;
|
||||
|
||||
if ($keyFilePath) {
|
||||
if (!file_exists($keyFilePath)) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Keyfile path %s does not exist.',
|
||||
$keyFilePath
|
||||
));
|
||||
}
|
||||
|
||||
$options['keyFile'] = self::jsonDecode(file_get_contents($keyFilePath), true);
|
||||
}
|
||||
|
||||
$rw = $connection->requestWrapper();
|
||||
|
||||
$keyFile = $options['keyFile'] ?? null;
|
||||
if ($keyFile) {
|
||||
$scopes = $options['scopes'] ?? $rw->scopes();
|
||||
|
||||
$credentials = CredentialsLoader::makeCredentials($scopes, $keyFile);
|
||||
} elseif (isset($options['credentialsFetcher'])) {
|
||||
$credentials = $options['credentialsFetcher'];
|
||||
} else {
|
||||
$credentials = $rw->getCredentialsFetcher();
|
||||
}
|
||||
|
||||
//@codeCoverageIgnoreStart
|
||||
if (!($credentials instanceof SignBlobInterface)) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'Credentials object is of type `%s` and is not valid for signing.',
|
||||
get_class($credentials)
|
||||
));
|
||||
}
|
||||
//@codeCoverageIgnoreEnd
|
||||
|
||||
unset(
|
||||
$options['keyFilePath'],
|
||||
$options['keyFile'],
|
||||
$options['credentialsFetcher'],
|
||||
$options['scopes']
|
||||
);
|
||||
|
||||
return [$credentials, $options];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add parameters common to all signed URL versions.
|
||||
*
|
||||
* @param int|null $generation
|
||||
* @param array $params
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
private function addCommonParams($generation, array $params, array $options)
|
||||
{
|
||||
if ($options['responseType']) {
|
||||
$params['response-content-type'] = $options['responseType'];
|
||||
}
|
||||
|
||||
if ($options['responseDisposition']) {
|
||||
$params['response-content-disposition'] = $options['responseDisposition'];
|
||||
} elseif ($options['saveAsName']) {
|
||||
$params['response-content-disposition'] = 'attachment; filename='
|
||||
. '"' . $options['saveAsName'] . '"';
|
||||
}
|
||||
|
||||
if ($generation) {
|
||||
$params['generation'] = $generation;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query string from an array.
|
||||
*
|
||||
* Note that this method does NOT urlencode keys or values.
|
||||
*
|
||||
* @param array $input
|
||||
* @return string
|
||||
*/
|
||||
private function buildQueryString(array $input)
|
||||
{
|
||||
$q = [];
|
||||
foreach ($input as $key => $val) {
|
||||
$q[] = $key . '=' . $val;
|
||||
}
|
||||
|
||||
return implode('&', $q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry logic for signBlob
|
||||
*
|
||||
* @param callable $signBlobFn A callable that perform the actual signBlob operation.
|
||||
* @param string $resourceName The resource name for logging or retry strategy determination.
|
||||
* @param array $args Arguments for the operations, include preconditions
|
||||
* @return string The signature genarated by signBlob.
|
||||
* @throws ServiceException If non-retryable error occur.
|
||||
* @throws \RuntimeException If retries are exhausted.
|
||||
*/
|
||||
private function retrySignBlob(callable $signBlobFn, string $resourceName = 'signBlob', array $args = [])
|
||||
{
|
||||
$attempt = 0;
|
||||
// Generate a retry decider function using the RetryTrait logic.
|
||||
$retryDecider = $this->getRestRetryFunction($resourceName, 'execute', $args);
|
||||
while (true) {
|
||||
++$attempt;
|
||||
try {
|
||||
// Attempt the operation
|
||||
return $signBlobFn();
|
||||
} catch (\Exception $exception) {
|
||||
if (!$retryDecider($exception, $attempt, self::MAX_RETRIES)) {
|
||||
// Non-retryable error
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
732
vendor/google/cloud-storage/src/StorageClient.php
vendored
Normal file
732
vendor/google/cloud-storage/src/StorageClient.php
vendored
Normal file
@@ -0,0 +1,732 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2015 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use Google\Auth\FetchAuthTokenInterface;
|
||||
use Google\Cloud\Core\ArrayTrait;
|
||||
use Google\Cloud\Core\ClientTrait;
|
||||
use Google\Cloud\Core\Exception\GoogleException;
|
||||
use Google\Cloud\Core\Iterator\ItemIterator;
|
||||
use Google\Cloud\Core\Iterator\PageIterator;
|
||||
use Google\Cloud\Core\Timestamp;
|
||||
use Google\Cloud\Core\Upload\SignedUrlUploader;
|
||||
use Google\Cloud\Storage\Connection\ConnectionInterface;
|
||||
use Google\Cloud\Storage\Connection\Rest;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* Google Cloud Storage allows you to store and retrieve data on Google's
|
||||
* infrastructure. Find more information at the
|
||||
* [Google Cloud Storage API docs](https://developers.google.com/storage).
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* use Google\Cloud\Storage\StorageClient;
|
||||
*
|
||||
* $storage = new StorageClient();
|
||||
* ```
|
||||
*/
|
||||
class StorageClient
|
||||
{
|
||||
use ArrayTrait;
|
||||
use ClientTrait;
|
||||
|
||||
const VERSION = '1.49.0';
|
||||
|
||||
const FULL_CONTROL_SCOPE = 'https://www.googleapis.com/auth/devstorage.full_control';
|
||||
const READ_ONLY_SCOPE = 'https://www.googleapis.com/auth/devstorage.read_only';
|
||||
const READ_WRITE_SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write';
|
||||
|
||||
/**
|
||||
* Retry strategy to signify that we never want to retry an operation
|
||||
* even if the error is retryable.
|
||||
*
|
||||
* We can set $options['retryStrategy'] to one of "always", "never" and
|
||||
* "idempotent".
|
||||
*/
|
||||
const RETRY_NEVER = 'never';
|
||||
/**
|
||||
* Retry strategy to signify that we always want to retry an operation.
|
||||
*/
|
||||
const RETRY_ALWAYS = 'always';
|
||||
/**
|
||||
* This is the default. This signifies that we want to retry an operation
|
||||
* only if it is retryable and the error is retryable.
|
||||
*/
|
||||
const RETRY_IDEMPOTENT = 'idempotent';
|
||||
|
||||
/**
|
||||
* @var ConnectionInterface Represents a connection to Storage.
|
||||
* @internal
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* Create a Storage client.
|
||||
*
|
||||
* @param array $config [optional] {
|
||||
* Configuration options.
|
||||
*
|
||||
* @type string $apiEndpoint The hostname with optional port to use in
|
||||
* place of the default service endpoint. Example:
|
||||
* `foobar.com` or `foobar.com:1234`.
|
||||
* @type string $projectId The project ID from the Google Developer's
|
||||
* Console.
|
||||
* @type CacheItemPoolInterface $authCache A cache used storing access
|
||||
* tokens. **Defaults to** a simple in memory implementation.
|
||||
* @type array $authCacheOptions Cache configuration options.
|
||||
* @type callable $authHttpHandler A handler used to deliver Psr7
|
||||
* requests specifically for authentication.
|
||||
* @type FetchAuthTokenInterface $credentialsFetcher A credentials
|
||||
* fetcher instance.
|
||||
* @type callable $httpHandler A handler used to deliver Psr7 requests.
|
||||
* Only valid for requests sent over REST.
|
||||
* @type array $keyFile [DEPRECATED]
|
||||
* This option is being deprecated because of a potential security risk.
|
||||
* This option does not validate the credential configuration. The security
|
||||
* risk occurs when a credential configuration is accepted from a source
|
||||
* that is not under your control and used without validation on your side.
|
||||
* If you know that you will be loading credential configurations of a
|
||||
* specific type, it is recommended to create the credentials directly and
|
||||
* configure them using the `credentialsFetcher` option instead.
|
||||
* ```
|
||||
* use Google\Auth\Credentials\ServiceAccountCredentials;
|
||||
* $credentialsFetcher = new ServiceAccountCredentials($scopes, $json);
|
||||
* $creds = new StorageClient(['credentialsFetcher' => $creds]);
|
||||
* ```
|
||||
* This will ensure that an unexpected credential type with potential for
|
||||
* malicious intent is not loaded unintentionally. You might still have to do
|
||||
* validation for certain credential types.
|
||||
* If you are loading your credential configuration from an untrusted source and have
|
||||
* not mitigated the risks (e.g. by validating the configuration yourself), make
|
||||
* these changes as soon as possible to prevent security risks to your environment.
|
||||
* Regardless of the method used, it is always your responsibility to validate
|
||||
* configurations received from external sources.
|
||||
* @see https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
|
||||
* @type string $keyFilePath [DEPRECATED]
|
||||
* This option is being deprecated because of a potential security risk.
|
||||
* This option does not validate the credential configuration. The security
|
||||
* risk occurs when a credential configuration is accepted from a source
|
||||
* that is not under your control and used without validation on your side.
|
||||
* If you know that you will be loading credential configurations of a
|
||||
* specific type, it is recommended to create the credentials directly and
|
||||
* configure them using the `credentialsFetcher` option instead.
|
||||
* ```
|
||||
* use Google\Auth\Credentials\ServiceAccountCredentials;
|
||||
* $credentialsFetcher = new ServiceAccountCredentials($scopes, $json);
|
||||
* $creds = new StorageClient(['credentialsFetcher' => $creds]);
|
||||
* ```
|
||||
* This will ensure that an unexpected credential type with potential for
|
||||
* malicious intent is not loaded unintentionally. You might still have to do
|
||||
* validation for certain credential types.
|
||||
* If you are loading your credential configuration from an untrusted source and have
|
||||
* not mitigated the risks (e.g. by validating the configuration yourself), make
|
||||
* these changes as soon as possible to prevent security risks to your environment.
|
||||
* Regardless of the method used, it is always your responsibility to validate
|
||||
* configurations received from external sources.
|
||||
* @see https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
|
||||
* @type float $requestTimeout Seconds to wait before timing out the
|
||||
* request. **Defaults to** `0` with REST and `60` with gRPC.
|
||||
* @type int $retries Number of retries for a failed request.
|
||||
* **Defaults to** `3`.
|
||||
* @type string $retryStrategy Retry strategy to signify that we never
|
||||
* want to retry an operation even if the error is retryable.
|
||||
* **Defaults to** `StorageClient::RETRY_IDEMPOTENT`.
|
||||
* @type callable $restDelayFunction Executes a delay, defaults to
|
||||
* utilizing `usleep`. Function signature should match:
|
||||
* `function (int $delay) : void`.
|
||||
* @type callable $restCalcDelayFunction Sets the conditions for
|
||||
* determining how long to wait between attempts to retry. Function
|
||||
* signature should match: `function (int $attempt) : int`.
|
||||
* @type callable $restRetryFunction Sets the conditions for whether or
|
||||
* not a request should attempt to retry. Function signature should
|
||||
* match: `function (\Exception $ex) : bool`.
|
||||
* @type callable $restRetryListener Runs after the restRetryFunction.
|
||||
* This might be used to simply consume the exception and
|
||||
* $arguments b/w retries. This returns the new $arguments thus
|
||||
* allowing modification on demand for $arguments. For ex:
|
||||
* changing the headers in b/w retries.
|
||||
* @type array $scopes Scopes to be used for the request.
|
||||
* @type string $quotaProject Specifies a user project to bill for
|
||||
* access charges associated with the request.
|
||||
* }
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
if (!isset($config['scopes'])) {
|
||||
$config['scopes'] = [
|
||||
'https://www.googleapis.com/auth/iam',
|
||||
self::FULL_CONTROL_SCOPE,
|
||||
];
|
||||
}
|
||||
|
||||
$this->connection = new Rest($this->configureAuthentication($config) + [
|
||||
'projectId' => $this->projectId
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily instantiates a bucket.
|
||||
*
|
||||
* There are no network requests made at this point. To see the operations
|
||||
* that can be performed on a bucket please see {@see Bucket}.
|
||||
*
|
||||
* If `$userProject` is set to true, the current project ID (used to
|
||||
* instantiate the client) will be billed for all requests. If
|
||||
* `$userProject` is a project ID, given as a string, that project
|
||||
* will be billed for all requests. This only has an effect when the bucket
|
||||
* is not owned by the current or given project ID.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $bucket = $storage->bucket('my-bucket');
|
||||
* ```
|
||||
*
|
||||
* @param string $name The name of the bucket to request.
|
||||
* @param string|bool $userProject If true, the current Project ID
|
||||
* will be used. If a string, that string will be used as the
|
||||
* userProject argument, and that project will be billed for the
|
||||
* request. **Defaults to** `false`.
|
||||
* @param array $options [optional] {
|
||||
* Configuration Options.
|
||||
*
|
||||
* @type bool $softDeleted If set to true, only soft-deleted bucket versions
|
||||
* are listed as distinct results in order of bucket name and generation
|
||||
* number. The default value is false.
|
||||
* @type string $generation If present, selects a specific soft-deleted version
|
||||
* of this bucket instead of the live version. This parameter is required if
|
||||
* softDeleted is set to true.
|
||||
* }
|
||||
* @return Bucket
|
||||
*/
|
||||
public function bucket($name, $userProject = false, array $options = [])
|
||||
{
|
||||
if (!$userProject) {
|
||||
$userProject = null;
|
||||
} elseif (!is_string($userProject)) {
|
||||
$userProject = $this->projectId;
|
||||
}
|
||||
|
||||
return new Bucket($this->connection, $name, $options + [
|
||||
'requesterProjectId' => $userProject
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all buckets in the project.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $buckets = $storage->buckets();
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* // Get all buckets beginning with the prefix 'album'.
|
||||
* $buckets = $storage->buckets([
|
||||
* 'prefix' => 'album'
|
||||
* ]);
|
||||
*
|
||||
* foreach ($buckets as $bucket) {
|
||||
* echo $bucket->name() . PHP_EOL;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/buckets/list Buckets list API documentation.
|
||||
*
|
||||
* @param array $options [optional] {
|
||||
* Configuration options.
|
||||
*
|
||||
* @type int $maxResults Maximum number of results to return per
|
||||
* requested page.
|
||||
* @type int $resultLimit Limit the number of results returned in total.
|
||||
* **Defaults to** `0` (return all results).
|
||||
* @type string $pageToken A previously-returned page token used to
|
||||
* resume the loading of results from a specific point.
|
||||
* @type string $prefix Filter results with this prefix.
|
||||
* @type string $projection Determines which properties to return. May
|
||||
* be either 'full' or 'noAcl'.
|
||||
* @type string $fields Selector which will cause the response to only
|
||||
* return the specified fields.
|
||||
* @type string $userProject If set, this is the ID of the project which
|
||||
* will be billed for the request.
|
||||
* @type bool $softDeleted If set to true, only soft-deleted bucket versions
|
||||
* are listed as distinct results in order of bucket name and generation
|
||||
* number. The default value is false.
|
||||
* @type bool $bucketUserProject If true, each returned instance will
|
||||
* have `$userProject` set to the value of `$options.userProject`.
|
||||
* If false, `$options.userProject` will be used ONLY for the
|
||||
* listBuckets operation. If `$options.userProject` is not set,
|
||||
* this option has no effect. **Defaults to** `true`.
|
||||
* @type bool $returnPartialSuccess If true, the returned iterator will contain an
|
||||
* `unreachable` property with a list of buckets that were not retrieved.
|
||||
* **Note:** If set to false (default) and unreachable buckets are found,
|
||||
* the operation will throw an exception.
|
||||
*
|
||||
* }
|
||||
* @return BucketIterator<Bucket>
|
||||
* @throws GoogleException When a project ID has not been detected.
|
||||
*/
|
||||
public function buckets(array $options = [])
|
||||
{
|
||||
$this->requireProjectId();
|
||||
$resultLimit = $this->pluck('resultLimit', $options, false);
|
||||
$bucketUserProject = $this->pluck('bucketUserProject', $options, null) ?? true;
|
||||
$userProject = $bucketUserProject ? ($options['userProject'] ?? null) : null;
|
||||
|
||||
$unreachable = new \ArrayObject();
|
||||
|
||||
$apiCall = [$this->connection, 'listBuckets'];
|
||||
$callDelegate = function (array $args) use ($apiCall, $unreachable) {
|
||||
$response = call_user_func($apiCall, $args);
|
||||
if (isset($response['unreachable']) && is_array($response['unreachable'])) {
|
||||
$current = $unreachable->getArrayCopy();
|
||||
$updated = array_unique(array_merge($current, $response['unreachable']));
|
||||
$unreachable->exchangeArray($updated);
|
||||
}
|
||||
return $response;
|
||||
};
|
||||
|
||||
// Return the new BucketIterator with the wrapped unreachable bucket
|
||||
return new BucketIterator(
|
||||
new PageIterator(
|
||||
function (array $bucket) use ($userProject) {
|
||||
return new Bucket(
|
||||
$this->connection,
|
||||
$bucket['name'],
|
||||
$bucket + ['requesterProjectId' => $userProject]
|
||||
);
|
||||
},
|
||||
$callDelegate,
|
||||
$options + ['project' => $this->projectId],
|
||||
['resultLimit' => $resultLimit]
|
||||
),
|
||||
$unreachable
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores a soft-deleted bucket.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $bucket = $storage->bucket->restore('my-bucket');
|
||||
* ```
|
||||
*
|
||||
* @param string $name The name of the bucket to restore.
|
||||
* @param string $generation The specific version of the bucket to be restored.
|
||||
* @param array $options [optional] {
|
||||
* Configuration Options.
|
||||
*
|
||||
* @type string $projection Determines which properties to return. May
|
||||
* be either `"full"` or `"noAcl"`. **Defaults to** `"noAcl"`,
|
||||
* unless the bucket resource specifies acl or defaultObjectAcl
|
||||
* properties, when it defaults to `"full"`.
|
||||
* }
|
||||
* @return Bucket
|
||||
*/
|
||||
public function restore(string $name, string $generation, array $options = [])
|
||||
{
|
||||
$res = $this->connection->restoreBucket([
|
||||
'bucket' => $name,
|
||||
'generation' => $generation,
|
||||
] + $options);
|
||||
return new Bucket(
|
||||
$this->connection,
|
||||
$name
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a bucket. Bucket names must be unique as Cloud Storage uses a flat
|
||||
* namespace. For more information please see
|
||||
* [bucket name requirements](https://cloud.google.com/storage/docs/naming#requirements)
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $bucket = $storage->createBucket('bucket');
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* // Create a bucket with logging enabled.
|
||||
* $bucket = $storage->createBucket('myBeautifulBucket', [
|
||||
* 'logging' => [
|
||||
* 'logBucket' => 'bucketToLogTo',
|
||||
* 'logObjectPrefix' => 'myPrefix'
|
||||
* ]
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @see https://cloud.google.com/storage/docs/json_api/v1/buckets/insert Buckets insert API documentation.
|
||||
*
|
||||
* @param string $name Name of the bucket to be created.
|
||||
* @codingStandardsIgnoreStart
|
||||
* @param array $options [optional] {
|
||||
* Configuration options.
|
||||
*
|
||||
* @type string $predefinedAcl Predefined ACL to apply to the bucket.
|
||||
* Acceptable values include, `"authenticatedRead"`,
|
||||
* `"bucketOwnerFullControl"`, `"bucketOwnerRead"`, `"private"`,
|
||||
* `"projectPrivate"`, and `"publicRead"`.
|
||||
* @type string $predefinedDefaultObjectAcl Apply a predefined set of
|
||||
* default object access controls to this bucket.
|
||||
* @type bool $enableObjectRetention Whether object retention should
|
||||
* be enabled on this bucket. For more information, refer to the
|
||||
* [Object Retention Lock](https://cloud.google.com/storage/docs/object-lock)
|
||||
* documentation.
|
||||
* @type string $projection Determines which properties to return. May
|
||||
* be either `"full"` or `"noAcl"`. **Defaults to** `"noAcl"`,
|
||||
* unless the bucket resource specifies acl or defaultObjectAcl
|
||||
* properties, when it defaults to `"full"`.
|
||||
* @type string $fields Selector which will cause the response to only
|
||||
* return the specified fields.
|
||||
* @type array $acl Access controls on the bucket.
|
||||
* @type array $cors The bucket's Cross-Origin Resource Sharing (CORS)
|
||||
* configuration.
|
||||
* @type array $defaultObjectAcl Default access controls to apply to new
|
||||
* objects when no ACL is provided.
|
||||
* @type array|Lifecycle $lifecycle The bucket's lifecycle configuration.
|
||||
* @type string $location The location of the bucket. If specifying
|
||||
* a dual-region, the `customPlacementConfig` property should be
|
||||
* set in conjunction. For more information, see
|
||||
* [Bucket Locations](https://cloud.google.com/storage/docs/locations).
|
||||
* **Defaults to** `"US"`.
|
||||
* @type array $hierarchicalNamespace The hierarchical namespace configuration
|
||||
* on this bucket.
|
||||
* @type array $customPlacementConfig The bucket's dual regions. For more
|
||||
* information, see
|
||||
* [Bucket Locations](https://cloud.google.com/storage/docs/locations).
|
||||
* @type array $logging The bucket's logging configuration, which
|
||||
* defines the destination bucket and optional name prefix for the
|
||||
* current bucket's logs.
|
||||
* @type string $storageClass The bucket's storage class. This defines
|
||||
* how objects in the bucket are stored and determines the SLA and
|
||||
* the cost of storage. Acceptable values include the following
|
||||
* strings: `"STANDARD"`, `"NEARLINE"`, `"COLDLINE"` and
|
||||
* `"ARCHIVE"`. Legacy values including `"MULTI_REGIONAL"`,
|
||||
* `"REGIONAL"` and `"DURABLE_REDUCED_AVAILABILITY"` are also
|
||||
* available, but should be avoided for new implementations. For
|
||||
* more information, refer to the
|
||||
* [Storage Classes](https://cloud.google.com/storage/docs/storage-classes)
|
||||
* documentation. **Defaults to** `"STANDARD"`.
|
||||
* @type array $autoclass The bucket's autoclass configuration.
|
||||
* Buckets can have either StorageClass OLM rules or Autoclass,
|
||||
* but not both. When Autoclass is enabled on a bucket, adding
|
||||
* StorageClass OLM rules will result in failure.
|
||||
* For more information, refer to
|
||||
* [Storage Autoclass](https://cloud.google.com/storage/docs/autoclass)
|
||||
* @type array $versioning The bucket's versioning configuration.
|
||||
* @type array $website The bucket's website configuration.
|
||||
* @type array $billing The bucket's billing configuration.
|
||||
* @type bool $billing.requesterPays When `true`, requests to this bucket
|
||||
* and objects within it must provide a project ID to which the
|
||||
* request will be billed.
|
||||
* @type array $labels The Bucket labels. Labels are represented as an
|
||||
* array of keys and values. To remove an existing label, set its
|
||||
* value to `null`.
|
||||
* @type string $userProject If set, this is the ID of the project which
|
||||
* will be billed for the request.
|
||||
* @type bool $bucketUserProject If true, the returned instance will
|
||||
* have `$userProject` set to the value of `$options.userProject`.
|
||||
* If false, `$options.userProject` will be used ONLY for the
|
||||
* createBucket operation. If `$options.userProject` is not set,
|
||||
* this option has no effect. **Defaults to** `true`.
|
||||
* @type array $encryption Encryption configuration used by default for
|
||||
* newly inserted objects.
|
||||
* @type string $encryption.defaultKmsKeyName A Cloud KMS Key used to
|
||||
* encrypt objects uploaded into this bucket. Should be in the
|
||||
* format
|
||||
* `projects/my-project/locations/kr-location/keyRings/my-kr/cryptoKeys/my-key`.
|
||||
* Please note the KMS key ring must use the same location as the
|
||||
* bucket.
|
||||
* @type bool $defaultEventBasedHold When `true`, newly created objects
|
||||
* in this bucket will be retained indefinitely until an event
|
||||
* occurs, signified by the hold's release.
|
||||
* @type array $retentionPolicy Defines the retention policy for a
|
||||
* bucket. In order to lock a retention policy, please see
|
||||
* {@see Bucket::lockRetentionPolicy()}.
|
||||
* @type int $retentionPolicy.retentionPeriod Specifies the retention
|
||||
* period for objects in seconds. During the retention period an
|
||||
* object cannot be overwritten or deleted. Retention period must
|
||||
* be greater than zero and less than 100 years.
|
||||
* @type array $iamConfiguration The bucket's IAM configuration.
|
||||
* @type bool $iamConfiguration.bucketPolicyOnly.enabled this is an alias
|
||||
* for $iamConfiguration.uniformBucketLevelAccess.
|
||||
* @type bool $iamConfiguration.uniformBucketLevelAccess.enabled If set and
|
||||
* true, access checks only use bucket-level IAM policies or
|
||||
* above. When enabled, requests attempting to view or manipulate
|
||||
* ACLs will fail with error code 400. **NOTE**: Before using
|
||||
* Uniform bucket-level access, please review the
|
||||
* [feature documentation](https://cloud.google.com/storage/docs/uniform-bucket-level-access),
|
||||
* as well as
|
||||
* [Should You Use uniform bucket-level access](https://cloud.google.com/storage/docs/uniform-bucket-level-access#should-you-use)
|
||||
* @type string $rpo Specifies the Turbo Replication setting for a dual-region bucket.
|
||||
* The possible values are DEFAULT and ASYNC_TURBO. Trying to set the rpo for a non dual-region
|
||||
* bucket will throw an exception. Non existence of this parameter is equivalent to it being DEFAULT.
|
||||
* }
|
||||
* @codingStandardsIgnoreEnd
|
||||
* @return Bucket
|
||||
* @throws GoogleException When a project ID has not been detected.
|
||||
*/
|
||||
public function createBucket($name, array $options = [])
|
||||
{
|
||||
$this->requireProjectId();
|
||||
|
||||
if (isset($options['lifecycle']) && $options['lifecycle'] instanceof Lifecycle) {
|
||||
$options['lifecycle'] = $options['lifecycle']->toArray();
|
||||
}
|
||||
|
||||
$bucketUserProject = $this->pluck('bucketUserProject', $options, false);
|
||||
$bucketUserProject = !is_null($bucketUserProject)
|
||||
? $bucketUserProject
|
||||
: true;
|
||||
$userProject = (isset($options['userProject']) && $bucketUserProject)
|
||||
? $options['userProject']
|
||||
: null;
|
||||
|
||||
$response = $this->connection->insertBucket($options + ['name' => $name, 'project' => $this->projectId]);
|
||||
return new Bucket(
|
||||
$this->connection,
|
||||
$name,
|
||||
$response + ['requesterProjectId' => $userProject]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this StorageClient as the handler for stream reading/writing.
|
||||
*
|
||||
* @param string $protocol The name of the protocol to use. **Defaults to** `gs`.
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function registerStreamWrapper($protocol = null)
|
||||
{
|
||||
return StreamWrapper::register($this, $protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the SteamWrapper
|
||||
*
|
||||
* @param string $protocol The name of the protocol to unregister. **Defaults to** `gs`.
|
||||
*/
|
||||
public function unregisterStreamWrapper($protocol = null)
|
||||
{
|
||||
StreamWrapper::unregister($protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an uploader to handle a Signed URL.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $uploader = $storage->signedUrlUploader($uri, fopen('/path/to/myfile.doc', 'r'));
|
||||
* ```
|
||||
*
|
||||
* @param string $uri The URI to accept an upload request.
|
||||
* @param string|resource|StreamInterface $data The data to be uploaded
|
||||
* @param array $options [optional] Configuration Options. Refer to
|
||||
* {@see \Google\Cloud\Core\Upload\AbstractUploader::__construct()}.
|
||||
* @return SignedUrlUploader
|
||||
*/
|
||||
public function signedUrlUploader($uri, $data, array $options = [])
|
||||
{
|
||||
return new SignedUrlUploader($this->connection->requestWrapper(), $data, $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Timestamp object.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $timestamp = $storage->timestamp(new \DateTime('2003-02-05 11:15:02.421827Z'));
|
||||
* ```
|
||||
*
|
||||
* @param \DateTimeInterface $timestamp The timestamp value.
|
||||
* @param int $nanoSeconds [optional] The number of nanoseconds in the timestamp.
|
||||
* @return Timestamp
|
||||
*/
|
||||
public function timestamp(\DateTimeInterface $timestamp, $nanoSeconds = null)
|
||||
{
|
||||
return new Timestamp($timestamp, $nanoSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the service account email associated with this client.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $serviceAccount = $storage->getServiceAccount();
|
||||
* ```
|
||||
*
|
||||
* @param array $options [optional] {
|
||||
* Configuration options.
|
||||
*
|
||||
* @type string $userProject If set, this is the ID of the project which
|
||||
* will be billed for the request.
|
||||
* }
|
||||
* @return string
|
||||
*/
|
||||
public function getServiceAccount(array $options = [])
|
||||
{
|
||||
$resp = $this->connection->getServiceAccount($options + ['projectId' => $this->projectId]);
|
||||
return $resp['email_address'];
|
||||
}
|
||||
|
||||
/**
|
||||
* List Service Account HMAC keys in the project.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $hmacKeys = $storage->hmacKeys();
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* // Get the HMAC keys associated with a Service Account email
|
||||
* $hmacKeys = $storage->hmacKeys([
|
||||
* 'serviceAccountEmail' => $serviceAccountEmail
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param array $options {
|
||||
* Configuration Options
|
||||
*
|
||||
* @type string $serviceAccountEmail If present, only keys for the given
|
||||
* service account are returned.
|
||||
* @type bool $showDeletedKeys Whether or not to show keys in the
|
||||
* DELETED state.
|
||||
* @type string $userProject If set, this is the ID of the project which
|
||||
* will be billed for the request.
|
||||
* @type string $projectId The project ID to use, if different from that
|
||||
* with which the client was created.
|
||||
* }
|
||||
* @return ItemIterator<HmacKey>
|
||||
*/
|
||||
public function hmacKeys(array $options = [])
|
||||
{
|
||||
$options += [
|
||||
'projectId' => $this->projectId
|
||||
];
|
||||
|
||||
if (!$options['projectId']) {
|
||||
$this->requireProjectId();
|
||||
}
|
||||
|
||||
$resultLimit = $this->pluck('resultLimit', $options, false);
|
||||
return new ItemIterator(
|
||||
new PageIterator(
|
||||
function (array $metadata) use ($options) {
|
||||
return $this->hmacKey(
|
||||
$metadata['accessId'],
|
||||
$options['projectId'],
|
||||
$metadata
|
||||
);
|
||||
},
|
||||
[$this->connection, 'listHmacKeys'],
|
||||
$options,
|
||||
['resultLimit' => $resultLimit]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily instantiate an HMAC Key instance using an Access ID.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $hmacKey = $storage->hmacKey($accessId);
|
||||
* ```
|
||||
*
|
||||
* @param string $accessId The ID of the HMAC Key.
|
||||
* @param string $projectId [optional] The project ID to use, if different
|
||||
* from that with which the client was created.
|
||||
* @param array $metadata [optional] HMAC key metadata.
|
||||
* @return HmacKey
|
||||
*/
|
||||
public function hmacKey($accessId, $projectId = null, array $metadata = [])
|
||||
{
|
||||
if (!$projectId) {
|
||||
$this->requireProjectId();
|
||||
}
|
||||
|
||||
return new HmacKey($this->connection, $projectId ?: $this->projectId, $accessId, $metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new HMAC key for the specified service account.
|
||||
*
|
||||
* Please note that the HMAC secret is only available at creation. Make sure
|
||||
* to note the secret after creation.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $response = $storage->createHmacKey('account@myProject.iam.gserviceaccount.com');
|
||||
* $secret = $response->secret();
|
||||
* ```
|
||||
*
|
||||
* @param string $serviceAccountEmail Email address of the service account.
|
||||
* @param array $options {
|
||||
* Configuration Options
|
||||
*
|
||||
* @type string $userProject If set, this is the ID of the project which
|
||||
* will be billed for the request. **NOTE**: This option is
|
||||
* currently ignored by Cloud Storage.
|
||||
* @type string $projectId The project ID to use, if different from that
|
||||
* with which the client was created.
|
||||
* }
|
||||
* @return CreatedHmacKey
|
||||
*/
|
||||
public function createHmacKey($serviceAccountEmail, array $options = [])
|
||||
{
|
||||
$options += [
|
||||
'projectId' => $this->projectId
|
||||
];
|
||||
|
||||
if (!$options['projectId']) {
|
||||
$this->requireProjectId();
|
||||
}
|
||||
|
||||
$res = $this->connection->createHmacKey([
|
||||
'projectId' => $options['projectId'],
|
||||
'serviceAccountEmail' => $serviceAccountEmail
|
||||
] + $options);
|
||||
|
||||
$key = new HmacKey(
|
||||
$this->connection,
|
||||
$options['projectId'],
|
||||
$res['metadata']['accessId'],
|
||||
$res['metadata']
|
||||
);
|
||||
|
||||
return new CreatedHmacKey($key, $res['secret']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception if no project ID available.
|
||||
*
|
||||
* @return void
|
||||
* @throws GoogleException
|
||||
*/
|
||||
private function requireProjectId()
|
||||
{
|
||||
if (!$this->projectId) {
|
||||
throw new GoogleException(
|
||||
'No project ID was provided, ' .
|
||||
'and we were unable to detect a default project ID.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
1476
vendor/google/cloud-storage/src/StorageObject.php
vendored
Normal file
1476
vendor/google/cloud-storage/src/StorageObject.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
880
vendor/google/cloud-storage/src/StreamWrapper.php
vendored
Normal file
880
vendor/google/cloud-storage/src/StreamWrapper.php
vendored
Normal file
@@ -0,0 +1,880 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use Google\Cloud\Core\Exception\NotFoundException;
|
||||
use Google\Cloud\Core\Exception\ServiceException;
|
||||
use GuzzleHttp\Psr7\CachingStream;
|
||||
|
||||
/**
|
||||
* A streamWrapper implementation for handling `gs://bucket/path/to/file.jpg`.
|
||||
* Note that you can only open a file with mode 'r', 'rb', 'rt', 'w', 'wb', 'wt', 'a', 'ab', or 'at'.
|
||||
*
|
||||
* See: http://php.net/manual/en/class.streamwrapper.php
|
||||
*/
|
||||
class StreamWrapper
|
||||
{
|
||||
const DEFAULT_PROTOCOL = 'gs';
|
||||
|
||||
const FILE_WRITABLE_MODE = 33206; // 100666 in octal
|
||||
const FILE_READABLE_MODE = 33060; // 100444 in octal
|
||||
const DIRECTORY_WRITABLE_MODE = 16895; // 40777 in octal
|
||||
const DIRECTORY_READABLE_MODE = 16676; // 40444 in octal
|
||||
|
||||
const TAIL_NAME_SUFFIX = '~';
|
||||
|
||||
/**
|
||||
* @var resource|null Must be public according to the PHP documentation.
|
||||
*
|
||||
* Contains array of context options in form ['protocol' => ['option' => value]].
|
||||
* Options used by StreamWrapper:
|
||||
*
|
||||
* flush (bool) `true`: fflush() will flush output buffer; `false`: fflush() will do nothing
|
||||
*/
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* @var \Psr\Http\Message\StreamInterface
|
||||
*/
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* @var string Protocol used to open this stream
|
||||
*/
|
||||
private $protocol;
|
||||
|
||||
/**
|
||||
* @var Bucket Reference to the bucket the opened file
|
||||
* lives in or will live in.
|
||||
*/
|
||||
private $bucket;
|
||||
|
||||
/**
|
||||
* @var string Name of the file opened by this stream.
|
||||
*/
|
||||
private $file;
|
||||
|
||||
/**
|
||||
* @var StorageClient[] $clients The default clients to use if using
|
||||
* global methods such as fopen on a stream wrapper. Keyed by protocol.
|
||||
*/
|
||||
private static $clients = [];
|
||||
|
||||
/**
|
||||
* @var ObjectIterator Used for iterating through a directory
|
||||
*/
|
||||
private $directoryIterator;
|
||||
|
||||
/**
|
||||
* @var StorageObject
|
||||
*/
|
||||
private $object;
|
||||
|
||||
/**
|
||||
* @var array Context options passed to stream_open(), used for append mode and flushing.
|
||||
*/
|
||||
private $options = [];
|
||||
|
||||
/**
|
||||
* @var bool `true`: fflush() will flush output buffer and redirect output to the "tail" object.
|
||||
*/
|
||||
private $flushing = false;
|
||||
|
||||
/**
|
||||
* @var string|null Content type for composed object. Will be filled on first composing.
|
||||
*/
|
||||
private $contentType = null;
|
||||
|
||||
/**
|
||||
* @var bool `true`: writing the "tail" object, next fflush() or fclose() will compose.
|
||||
*/
|
||||
private $composing = false;
|
||||
|
||||
/**
|
||||
* @var bool `true`: data has been written to the stream.
|
||||
*/
|
||||
private $dirty = false;
|
||||
|
||||
/**
|
||||
* Ensure we close the stream when this StreamWrapper is destroyed.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->stream_close();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when include/require is used on a stream.
|
||||
*/
|
||||
public function stream_set_option()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when touch is used on a stream. See:
|
||||
* https://www.php.net/manual/en/streamwrapper.stream-metadata.php
|
||||
*/
|
||||
public function stream_metadata($path, $option, $value)
|
||||
{
|
||||
if ($option == STREAM_META_TOUCH) {
|
||||
$this->openPath($path);
|
||||
return $this->touch();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty file if it does not exist.
|
||||
* @return bool Returns true if file exists or has been created, false otherwise.
|
||||
*/
|
||||
private function touch()
|
||||
{
|
||||
$object = $this->bucket->object($this->file);
|
||||
try {
|
||||
if (!$object->exists()) {
|
||||
$this->bucket->upload('', [
|
||||
'name' => $this->file
|
||||
]);
|
||||
}
|
||||
return true;
|
||||
} catch (NotFoundException $e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a StreamWrapper for reading and writing to Google Storage
|
||||
*
|
||||
* @param StorageClient $client The StorageClient configuration to use.
|
||||
* @param string $protocol The name of the protocol to use. **Defaults to**
|
||||
* `gs`.
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function register(StorageClient $client, $protocol = null)
|
||||
{
|
||||
$protocol = $protocol ?: self::DEFAULT_PROTOCOL;
|
||||
if (!in_array($protocol, stream_get_wrappers())) {
|
||||
if (!stream_wrapper_register($protocol, StreamWrapper::class, STREAM_IS_URL)) {
|
||||
throw new \RuntimeException("Failed to register '$protocol://' protocol");
|
||||
}
|
||||
self::$clients[$protocol] = $client;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the SteamWrapper
|
||||
*
|
||||
* @param string $protocol The name of the protocol to unregister. **Defaults
|
||||
* to** `gs`.
|
||||
*/
|
||||
public static function unregister($protocol = null)
|
||||
{
|
||||
$protocol = $protocol ?: self::DEFAULT_PROTOCOL;
|
||||
stream_wrapper_unregister($protocol);
|
||||
unset(self::$clients[$protocol]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default client to use for streams.
|
||||
*
|
||||
* @param string $protocol The name of the protocol to get the client for.
|
||||
* **Defaults to** `gs`.
|
||||
* @return StorageClient
|
||||
*/
|
||||
public static function getClient($protocol = null)
|
||||
{
|
||||
$protocol = $protocol ?: self::DEFAULT_PROTOCOL;
|
||||
return self::$clients[$protocol];
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for when a stream is opened. For reads, we need to
|
||||
* download the file to see if it can be opened.
|
||||
*
|
||||
* @param string $path The path of the resource to open
|
||||
* @param string $mode The fopen mode. Currently supports ('r', 'rb', 'rt', 'w', 'wb', 'wt', 'a', 'ab', 'at')
|
||||
* @param int $flags Bitwise options STREAM_USE_PATH|STREAM_REPORT_ERRORS|STREAM_MUST_SEEK
|
||||
* @param string $openedPath Will be set to the path on success if STREAM_USE_PATH option is set
|
||||
* @return bool
|
||||
*/
|
||||
public function stream_open($path, $mode, $flags, &$openedPath)
|
||||
{
|
||||
$this->openPath($path);
|
||||
|
||||
// strip off 'b' or 't' from the mode
|
||||
$mode = rtrim($mode, 'bt');
|
||||
|
||||
$options = [];
|
||||
if ($this->context) {
|
||||
$contextOptions = stream_context_get_options($this->context);
|
||||
if (array_key_exists($this->protocol, $contextOptions)) {
|
||||
$options = $contextOptions[$this->protocol] ?: [];
|
||||
}
|
||||
|
||||
if (isset($options['flush'])) {
|
||||
$this->flushing = (bool) $options['flush'];
|
||||
unset($options['flush']);
|
||||
}
|
||||
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
if ($mode == 'w') {
|
||||
$this->stream = new WriteStream(null, $options);
|
||||
$this->stream->setUploader(
|
||||
$this->bucket->getStreamableUploader(
|
||||
$this->stream,
|
||||
$options + ['name' => $this->file]
|
||||
)
|
||||
);
|
||||
} elseif ($mode == 'a') {
|
||||
try {
|
||||
$info = $this->bucket->object($this->file)->info();
|
||||
$this->composing = ($info['size'] > 0);
|
||||
} catch (NotFoundException $e) {
|
||||
}
|
||||
|
||||
$this->stream = new WriteStream(null, $options);
|
||||
$name = $this->file;
|
||||
if ($this->composing) {
|
||||
$name .= self::TAIL_NAME_SUFFIX;
|
||||
}
|
||||
$this->stream->setUploader(
|
||||
$this->bucket->getStreamableUploader(
|
||||
$this->stream,
|
||||
$options + ['name' => $name]
|
||||
)
|
||||
);
|
||||
} elseif ($mode == 'r') {
|
||||
try {
|
||||
// Lazy read from the source
|
||||
$options['restOptions']['stream'] = true;
|
||||
$this->stream = new ReadStream(
|
||||
$this->bucket->object($this->file)->downloadAsStream($options)
|
||||
);
|
||||
|
||||
// Wrap the response in a caching stream to make it seekable
|
||||
if (!$this->stream->isSeekable() && ($flags & STREAM_MUST_SEEK)) {
|
||||
$this->stream = new CachingStream($this->stream);
|
||||
}
|
||||
} catch (ServiceException $ex) {
|
||||
return $this->returnError($ex->getMessage(), $flags);
|
||||
}
|
||||
} else {
|
||||
return $this->returnError('Unknown stream_open mode.', $flags);
|
||||
}
|
||||
|
||||
if ($flags & STREAM_USE_PATH) {
|
||||
$openedPath = $path;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for when we try to read a certain number of bytes.
|
||||
*
|
||||
* @param int $count The number of bytes to read.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function stream_read($count)
|
||||
{
|
||||
return $this->stream->read($count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for when we try to write data to the stream.
|
||||
*
|
||||
* @param string $data The data to write
|
||||
*
|
||||
* @return int The number of bytes written.
|
||||
*/
|
||||
public function stream_write($data)
|
||||
{
|
||||
$result = $this->stream->write($data);
|
||||
$this->dirty = $this->dirty || (bool) $result;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for getting data about the stream.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function stream_stat()
|
||||
{
|
||||
$mode = $this->stream->isWritable()
|
||||
? self::FILE_WRITABLE_MODE
|
||||
: self::FILE_READABLE_MODE;
|
||||
return $this->makeStatArray([
|
||||
'mode' => $mode,
|
||||
'size' => $this->stream->getSize()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for checking to see if the stream is at the end of file.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function stream_eof()
|
||||
{
|
||||
return $this->stream->eof();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for trying to close the stream.
|
||||
*/
|
||||
public function stream_close()
|
||||
{
|
||||
if (isset($this->stream)) {
|
||||
$this->stream->close();
|
||||
}
|
||||
|
||||
if ($this->composing) {
|
||||
if ($this->dirty) {
|
||||
$this->compose();
|
||||
$this->dirty = false;
|
||||
}
|
||||
try {
|
||||
$this->bucket->object($this->file . self::TAIL_NAME_SUFFIX)->delete();
|
||||
} catch (NotFoundException $e) {
|
||||
}
|
||||
$this->composing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for trying to seek to a certain location in the stream.
|
||||
*
|
||||
* @param int $offset The stream offset to seek to
|
||||
* @param int $whence Flag for what the offset is relative to. See:
|
||||
* http://php.net/manual/en/streamwrapper.stream-seek.php
|
||||
* @return bool
|
||||
*/
|
||||
public function stream_seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
if ($this->stream->isSeekable()) {
|
||||
$this->stream->seek($offset, $whence);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callhack handler for inspecting our current position in the stream
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->stream->tell();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for trying to close an opened directory.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function dir_closedir()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for trying to open a directory.
|
||||
*
|
||||
* @param string $path The url directory to open
|
||||
* @param int $options Whether or not to enforce safe_mode
|
||||
* @return bool
|
||||
*/
|
||||
public function dir_opendir($path, $options)
|
||||
{
|
||||
$this->openPath($path);
|
||||
return $this->dir_rewinddir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for reading an entry from a directory handle.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public function dir_readdir()
|
||||
{
|
||||
$name = $this->directoryIterator->current();
|
||||
if ($name) {
|
||||
$this->directoryIterator->next();
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for rewind the directory handle.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function dir_rewinddir()
|
||||
{
|
||||
try {
|
||||
$iterator = $this->bucket->objects([
|
||||
'prefix' => $this->file,
|
||||
'fields' => 'items/name,nextPageToken'
|
||||
]);
|
||||
|
||||
// The delimiter options do not give us what we need, so instead we
|
||||
// list all results matching the given prefix, enumerate the
|
||||
// iterator, filter and transform results, and yield a fresh
|
||||
// generator containing only the directory listing.
|
||||
$this->directoryIterator = call_user_func(function () use ($iterator) {
|
||||
$yielded = [];
|
||||
$pathLen = strlen($this->makeDirectory($this->file));
|
||||
foreach ($iterator as $object) {
|
||||
$name = substr($object->name(), $pathLen);
|
||||
$parts = explode('/', $name);
|
||||
|
||||
// since the service call returns nested results and we only
|
||||
// want to yield results directly within the requested directory,
|
||||
// check if we've already yielded this value.
|
||||
if ($parts[0] === '' || in_array($parts[0], $yielded)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$yielded[] = $parts[0];
|
||||
yield $name => $parts[0];
|
||||
}
|
||||
});
|
||||
} catch (ServiceException $e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for trying to create a directory. If no file path is specified,
|
||||
* or STREAM_MKDIR_RECURSIVE option is set, then create the bucket if it does not exist.
|
||||
*
|
||||
* @param string $path The url directory to create
|
||||
* @param int $mode The permissions on the directory
|
||||
* @param int $options Bitwise mask of options. STREAM_MKDIR_RECURSIVE
|
||||
* @return bool
|
||||
*/
|
||||
public function mkdir($path, $mode, $options)
|
||||
{
|
||||
$path = $this->makeDirectory($path);
|
||||
$client = $this->openPath($path);
|
||||
$predefinedAcl = $this->determineAclFromMode($mode);
|
||||
|
||||
try {
|
||||
if ($options & STREAM_MKDIR_RECURSIVE || $this->file == '') {
|
||||
if (!$this->bucket->exists()) {
|
||||
$client->createBucket($this->bucket->name(), [
|
||||
'predefinedAcl' => $predefinedAcl,
|
||||
'predefinedDefaultObjectAcl' => $predefinedAcl
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// If the file name is empty, we were trying to create a bucket. In this case,
|
||||
// don't create the placeholder file.
|
||||
if ($this->file != '') {
|
||||
$bucketInfo = $this->bucket->info();
|
||||
$ublEnabled = isset($bucketInfo['iamConfiguration']['uniformBucketLevelAccess']) &&
|
||||
$bucketInfo['iamConfiguration']['uniformBucketLevelAccess']['enabled'] === true;
|
||||
|
||||
// if bucket has uniform bucket level access enabled, don't set ACLs.
|
||||
$acl = [];
|
||||
if (!$ublEnabled) {
|
||||
$acl = [
|
||||
'predefinedAcl' => $predefinedAcl
|
||||
];
|
||||
}
|
||||
|
||||
// Fake a directory by creating an empty placeholder file whose name ends in '/'
|
||||
$this->bucket->upload('', [
|
||||
'name' => $this->file,
|
||||
] + $acl);
|
||||
}
|
||||
} catch (ServiceException $e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for trying to move a file or directory.
|
||||
*
|
||||
* @param string $from The URL to the current file
|
||||
* @param string $to The URL of the new file location
|
||||
* @return bool
|
||||
*/
|
||||
public function rename($from, $to)
|
||||
{
|
||||
$this->openPath($from);
|
||||
$destination = (array) parse_url($to) + [
|
||||
'path' => '',
|
||||
'host' => ''
|
||||
];
|
||||
|
||||
$destinationBucket = $destination['host'];
|
||||
$destinationPath = substr($destination['path'], 1);
|
||||
|
||||
// loop through to rename file and children, if given path is a directory.
|
||||
$objects = $this->bucket->objects([
|
||||
'prefix' => $this->file
|
||||
]);
|
||||
|
||||
foreach ($objects as $obj) {
|
||||
$oldName = $obj->name();
|
||||
$newPath = str_replace($this->file, $destinationPath, $oldName);
|
||||
try {
|
||||
$obj->rename($newPath, [
|
||||
'destinationBucket' => $destinationBucket
|
||||
]);
|
||||
} catch (ServiceException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for trying to remove a directory or a bucket. If the path is empty
|
||||
* or '/', the bucket will be deleted.
|
||||
*
|
||||
* Note that the STREAM_MKDIR_RECURSIVE flag is ignored because the option cannot
|
||||
* be set via the `rmdir()` function.
|
||||
*
|
||||
* @param string $path The URL directory to remove. If the path is empty or is '/',
|
||||
* This will attempt to destroy the bucket.
|
||||
* @param int $options Bitwise mask of options.
|
||||
* @return bool
|
||||
*/
|
||||
public function rmdir($path, $options)
|
||||
{
|
||||
$path = $this->makeDirectory($path);
|
||||
$this->openPath($path);
|
||||
|
||||
try {
|
||||
if ($this->file == '') {
|
||||
$this->bucket->delete();
|
||||
return true;
|
||||
} else {
|
||||
return $this->unlink($path);
|
||||
}
|
||||
} catch (ServiceException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for retrieving the underlaying resource
|
||||
*
|
||||
* @param int $castAs STREAM_CAST_FOR_SELECT|STREAM_CAST_AS_STREAM
|
||||
* @return resource|bool
|
||||
*/
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for deleting a file
|
||||
*
|
||||
* @param string $path The URL of the file to delete
|
||||
* @return bool
|
||||
*/
|
||||
public function unlink($path)
|
||||
{
|
||||
$client = $this->openPath($path);
|
||||
$object = $this->bucket->object($this->file);
|
||||
|
||||
try {
|
||||
$object->delete();
|
||||
return true;
|
||||
} catch (ServiceException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for retrieving information about a file
|
||||
*
|
||||
* @param string $path The URI to the file
|
||||
* @param int $flags Bitwise mask of options
|
||||
* @return array|bool
|
||||
*/
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$client = $this->openPath($path);
|
||||
|
||||
// if directory
|
||||
$dir = $this->getDirectoryInfo($this->file);
|
||||
if ($dir) {
|
||||
return $this->urlStatDirectory($dir);
|
||||
}
|
||||
|
||||
return $this->urlStatFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for fflush() function.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function stream_flush()
|
||||
{
|
||||
if (!$this->flushing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->dirty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isset($this->stream)) {
|
||||
$this->stream->close();
|
||||
}
|
||||
|
||||
if ($this->composing) {
|
||||
$this->compose();
|
||||
}
|
||||
|
||||
$options = $this->options;
|
||||
$this->stream = new WriteStream(null, $options);
|
||||
$this->stream->setUploader(
|
||||
$this->bucket->getStreamableUploader(
|
||||
$this->stream,
|
||||
$options + ['name' => $this->file . self::TAIL_NAME_SUFFIX]
|
||||
)
|
||||
);
|
||||
$this->composing = true;
|
||||
$this->dirty = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the URL and set protocol, filename and bucket.
|
||||
*
|
||||
* @param string $path URL to open
|
||||
* @return StorageClient
|
||||
*/
|
||||
private function openPath($path)
|
||||
{
|
||||
$url = (array) parse_url($path) + [
|
||||
'scheme' => '',
|
||||
'path' => '',
|
||||
'host' => ''
|
||||
];
|
||||
$this->protocol = $url['scheme'];
|
||||
$this->file = ltrim($url['path'], '/');
|
||||
$client = self::getClient($this->protocol);
|
||||
$this->bucket = $client->bucket($url['host']);
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a path, ensure that we return a path that looks like a directory
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
private function makeDirectory($path)
|
||||
{
|
||||
if ($path == '' or $path == '/') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (substr($path, -1) == '/') {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return $path . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the `url_stat` response for a directory
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
private function urlStatDirectory(StorageObject $object)
|
||||
{
|
||||
$stats = [];
|
||||
$info = $object->info();
|
||||
|
||||
// equivalent to 40777 and 40444 in octal
|
||||
$stats['mode'] = $this->bucket->isWritable()
|
||||
? self::DIRECTORY_WRITABLE_MODE
|
||||
: self::DIRECTORY_READABLE_MODE;
|
||||
$this->statsFromFileInfo($info, $stats);
|
||||
|
||||
return $this->makeStatArray($stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the `url_stat` response for a file
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
private function urlStatFile()
|
||||
{
|
||||
try {
|
||||
$this->object = $this->bucket->object($this->file);
|
||||
$info = $this->object->info();
|
||||
} catch (ServiceException $e) {
|
||||
// couldn't stat file
|
||||
return false;
|
||||
}
|
||||
|
||||
// equivalent to 100666 and 100444 in octal
|
||||
$stats = [
|
||||
'mode' => $this->bucket->isWritable()
|
||||
? self::FILE_WRITABLE_MODE
|
||||
: self::FILE_READABLE_MODE
|
||||
];
|
||||
$this->statsFromFileInfo($info, $stats);
|
||||
return $this->makeStatArray($stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a `StorageObject` info array, extract the available fields into the
|
||||
* provided `$stats` array.
|
||||
*
|
||||
* @param array $info Array provided from a `StorageObject`.
|
||||
* @param array $stats Array to put the calculated stats into.
|
||||
*/
|
||||
private function statsFromFileInfo(array &$info, array &$stats)
|
||||
{
|
||||
$stats['size'] = (isset($info['size']))
|
||||
? (int) $info['size']
|
||||
: null;
|
||||
|
||||
$stats['mtime'] = (isset($info['updated']))
|
||||
? strtotime($info['updated'])
|
||||
: null;
|
||||
|
||||
$stats['ctime'] = (isset($info['timeCreated']))
|
||||
? strtotime($info['timeCreated'])
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the given path as a directory.
|
||||
*
|
||||
* In list objects calls, directories are returned with a trailing slash. By
|
||||
* providing the given path with a trailing slash as a list prefix, we can
|
||||
* check whether the given path exists as a directory.
|
||||
*
|
||||
* If the path does not exist or is not a directory, return null.
|
||||
*
|
||||
* @param string $path
|
||||
* @return StorageObject|null
|
||||
*/
|
||||
private function getDirectoryInfo($path)
|
||||
{
|
||||
$scan = $this->bucket->objects([
|
||||
'prefix' => $this->makeDirectory($path),
|
||||
'resultLimit' => 1,
|
||||
'fields' => 'items/name,items/size,items/updated,items/timeCreated,nextPageToken'
|
||||
]);
|
||||
|
||||
return $scan->current();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the associative array that a `stat()` response expects using the
|
||||
* provided stats. Defaults the remaining fields to 0.
|
||||
*
|
||||
* @param array $stats Sparse stats entries to set.
|
||||
* @return array
|
||||
*/
|
||||
private function makeStatArray($stats)
|
||||
{
|
||||
return array_merge(
|
||||
array_fill_keys([
|
||||
'dev',
|
||||
'ino',
|
||||
'mode',
|
||||
'nlink',
|
||||
'uid',
|
||||
'gid',
|
||||
'rdev',
|
||||
'size',
|
||||
'atime',
|
||||
'mtime',
|
||||
'ctime',
|
||||
'blksize',
|
||||
'blocks'
|
||||
], 0),
|
||||
$stats
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for whether or not to trigger an error or just return false on an error.
|
||||
*
|
||||
* @param string $message The PHP error message to emit.
|
||||
* @param int $flags Bitwise mask of options (STREAM_REPORT_ERRORS)
|
||||
* @return bool Returns false
|
||||
*/
|
||||
private function returnError($message, $flags)
|
||||
{
|
||||
if ($flags & STREAM_REPORT_ERRORS) {
|
||||
trigger_error($message, E_USER_WARNING);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for determining which predefinedAcl to use given a mode.
|
||||
*
|
||||
* @param int $mode Decimal representation of the file system permissions
|
||||
* @return string
|
||||
*/
|
||||
private function determineAclFromMode($mode)
|
||||
{
|
||||
if ($mode & 0004) {
|
||||
// If any user can read, assume it should be publicRead.
|
||||
return 'publicRead';
|
||||
} elseif ($mode & 0040) {
|
||||
// If any group user can read, assume it should be projectPrivate.
|
||||
return 'projectPrivate';
|
||||
}
|
||||
|
||||
// Otherwise, assume only the project/bucket owner can use the bucket.
|
||||
return 'private';
|
||||
}
|
||||
|
||||
private function compose()
|
||||
{
|
||||
if (!isset($this->contentType)) {
|
||||
$info = $this->bucket->object($this->file)->info();
|
||||
$this->contentType = $info['contentType'] ?: 'application/octet-stream';
|
||||
}
|
||||
$options = ['destination' => ['contentType' => $this->contentType]];
|
||||
$this->bucket->compose([$this->file, $this->file . self::TAIL_NAME_SUFFIX], $this->file, $options);
|
||||
}
|
||||
}
|
||||
110
vendor/google/cloud-storage/src/WriteStream.php
vendored
Normal file
110
vendor/google/cloud-storage/src/WriteStream.php
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google\Cloud\Storage;
|
||||
|
||||
use Google\Cloud\Core\Upload\AbstractUploader;
|
||||
use GuzzleHttp\Psr7\BufferStream;
|
||||
use GuzzleHttp\Psr7\StreamDecoratorTrait;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* A Stream implementation that uploads in chunks to a provided uploader when
|
||||
* we reach a certain chunkSize. Upon `close`, we will upload the remaining chunk.
|
||||
*/
|
||||
class WriteStream implements StreamInterface
|
||||
{
|
||||
use StreamDecoratorTrait;
|
||||
|
||||
private $uploader;
|
||||
private $stream;
|
||||
private $chunkSize = 262144;
|
||||
private $hasWritten = false;
|
||||
|
||||
/**
|
||||
* Create a new WriteStream instance
|
||||
*
|
||||
* @param AbstractUploader $uploader The uploader to use.
|
||||
* @param array $options [optional] {
|
||||
* Configuration options.
|
||||
*
|
||||
* @type int $chunkSize The size of the buffer above which we attempt to
|
||||
* upload data
|
||||
* }
|
||||
*/
|
||||
public function __construct(?AbstractUploader $uploader = null, $options = [])
|
||||
{
|
||||
if ($uploader) {
|
||||
$this->setUploader($uploader);
|
||||
}
|
||||
if (array_key_exists('chunkSize', $options)) {
|
||||
$this->chunkSize = $options['chunkSize'];
|
||||
}
|
||||
$this->stream = new BufferStream($this->chunkSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the stream. Uploads any remaining data.
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
if ($this->uploader && $this->hasWritten) {
|
||||
$this->uploader->upload();
|
||||
$this->uploader = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to the stream. If we pass the chunkable size, upload the available chunk.
|
||||
*
|
||||
* @param string $data Data to write
|
||||
* @return int The number of bytes written
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function write($data): int
|
||||
{
|
||||
if (!isset($this->uploader)) {
|
||||
throw new \RuntimeException('No uploader set.');
|
||||
}
|
||||
|
||||
// Ensure we have a resume uri here because we need to create the streaming
|
||||
// upload before we have data (size of 0).
|
||||
$this->uploader->getResumeUri();
|
||||
$this->hasWritten = true;
|
||||
|
||||
if (!$this->stream->write($data)) {
|
||||
$this->uploader->upload($this->getChunkedWriteSize());
|
||||
}
|
||||
return strlen($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the uploader for this class. You may need to set this after initialization
|
||||
* if the uploader depends on this stream.
|
||||
*
|
||||
* @param AbstractUploader $uploader The new uploader to use.
|
||||
*/
|
||||
public function setUploader($uploader): void
|
||||
{
|
||||
$this->uploader = $uploader;
|
||||
}
|
||||
|
||||
private function getChunkedWriteSize(): int
|
||||
{
|
||||
return (int) floor($this->getSize() / $this->chunkSize) * $this->chunkSize;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user