| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885 |
- <?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\JsonTrait;
- use Google\Cloud\Core\Timestamp;
- use Google\Cloud\Storage\Connection\ConnectionInterface;
- /**
- * Provides common methods for signing storage URLs.
- *
- * @internal
- */
- class SigningHelper
- {
- use ArrayTrait;
- use JsonTrait;
- 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';
- /**
- * 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);
- $signature = $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($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,
- '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);
- } 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['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);
- }
- }
|