Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,14 @@
"vierbergenlars/php-semver": "^3.0",
"google/proto-client": "^0.20.0",
"google/gax": "^0.20.0",
"symfony/lock": "3.3.x-dev#1ba6ac9"
"symfony/lock": "3.3.x-dev#1ba6ac9",
"phpseclib/phpseclib": "^2.0"
},
"suggest": {
"google/gax": "Required to support gRPC",
"google/proto-client": "Required to support gRPC",
"symfony/lock": "Required for the Spanner cached based session pool. Please require the following commit: 3.3.x-dev#1ba6ac9"
"google/proto-client-php": "Required to support gRPC",
"symfony/lock": "Required for the Spanner cached based session pool. Please require the following commit: 3.3.x-dev#1ba6ac9",
"phpseclib/phpseclib": "May be used in place of OpenSSL for creating signed Cloud Storage URLs."
},
"autoload": {
"psr-4": {
Expand Down
19 changes: 19 additions & 0 deletions dev/src/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,22 @@ function stub($extends, array $args = [], array $props = [])
$reflection = new \ReflectionClass($name);
return $reflection->newInstanceArgs($args);
}

/**
* Get a trait implementation.
*
* @param string $trait The fully-qualified name of the trait to implement.
* @return mixed
*/
function impl($trait)
{
$tpl = 'class %s { use %s; public function call($fn, array $args = []) { return call_user_func_array([$this, $fn], $args); } }';

$name = 'Trait'. sha1($trait);

if (!class_exists($name)) {
eval(sprintf($tpl, $name, $trait));
}

return new $name;
}
10 changes: 10 additions & 0 deletions src/Core/GrpcTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ public function setRequestWrapper(GrpcRequestWrapper $requestWrapper)
$this->requestWrapper = $requestWrapper;
}

/**
* Get the GrpcRequestWrapper.
*
* @return GrpcRequestWrapper|null
*/
public function requestWrapper()
{
return $this->requestWrapper;
}

/**
* Delivers a request.
*
Expand Down
10 changes: 10 additions & 0 deletions src/Core/RequestWrapperTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ public function setCommonDefaults(array $config)
$this->requestTimeout = $config['requestTimeout'];
}

/**
* Get the Keyfile.
*
* @return array
*/
public function keyFile()
{
return $this->keyFile;
}

/**
* Gets the credentials fetcher and sets up caching. Precedence begins with
* user supplied credentials fetcher instance, followed by a reference to a
Expand Down
10 changes: 10 additions & 0 deletions src/Core/RestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ public function setRequestWrapper(RequestWrapper $requestWrapper)
$this->requestWrapper = $requestWrapper;
}

/**
* Get the RequestWrapper.
*
* @return RequestWrapper|null
*/
public function requestWrapper()
{
return $this->requestWrapper;
}

/**
* Delivers a request built from the service definition.
*
Expand Down
3 changes: 3 additions & 0 deletions src/Core/Upload/AbstractUploader.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ abstract class AbstractUploader

const UPLOAD_TYPE_RESUMABLE = 'resumable';
const UPLOAD_TYPE_MULTIPART = 'multipart';
const UPLOAD_TYPE_STREAMABLE = 'streamable';
const UPLOAD_TYPE_SIGNED = 'signed';

const RESUMABLE_LIMIT = 5000000;

/**
Expand Down
55 changes: 36 additions & 19 deletions src/Core/Upload/ResumableUploader.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
class ResumableUploader extends AbstractUploader
{
use JsonTrait;

/**
* @var callable
*/
Expand All @@ -47,20 +47,23 @@ class ResumableUploader extends AbstractUploader
* @var string
*/
private $resumeUri;

/**
* Extend the parent constructor with the specific
* for resumable upload option "uploadProgressCallback"
*
* @param RequestWrapper $requestWrapper
* @param string|resource|StreamInterface $data
* @param string $uri
* @param array $options [optional] {
* Optional configuration.
*
* @type array $metadata Metadata on the resource.
* @type callable $uploadProgressCallback to be called on each
* successfully uploaded chunk.
* @type callable $uploadProgressCallback The given callable
* function/method will be called after each successfully uploaded
* chunk. The callable function/method will receive the number of
* uploaded bytes after each uploaded chunk as a parameter to this
* callable. It's useful if you want to create a progress bar when
* using resumable upload type together with $chunkSize parameter.
* If $chunkSize is not set the callable function/method will be
* called only once after the successful file upload.
* @type int $chunkSize Size of the chunks to send incrementally during
* a resumable upload. Must be in multiples of 262144 bytes.
* @type array $restOptions HTTP client specific configuration options.
Expand All @@ -78,8 +81,10 @@ public function __construct(
parent::__construct($requestWrapper, $data, $uri, $options);

// Set uploadProgressCallback if it's passed as an option.
if (isset($options['uploadProgressCallback'])) {
if (isset($options['uploadProgressCallback']) && is_callable($options['uploadProgressCallback'])) {
$this->uploadProgressCallback = $options['uploadProgressCallback'];
} elseif (isset($options['uploadProgressCallback'])) {
throw new \InvalidArgumentException('$options.uploadProgressCallback must be a callable.');
}
}

Expand Down Expand Up @@ -114,7 +119,7 @@ public function resume($resumeUri)
$response = $this->getStatusResponse();

if ($response->getBody()->getSize() > 0) {
return $this->jsonDecode($response->getBody(), true);
return $this->decodeResponse($response);
}

$this->rangeStart = $this->getRangeStart($response->getHeaderLine('Range'));
Expand All @@ -141,11 +146,11 @@ public function upload()
$this->chunkSize ?: - 1,
$rangeStart
);

$currStreamLimitSize = $data->getSize();

$rangeEnd = $rangeStart + ($currStreamLimitSize - 1);

$headers = [
'Content-Length' => $currStreamLimitSize,
'Content-Type' => $this->contentType,
Expand All @@ -167,14 +172,25 @@ public function upload()
$ex->getCode()
);
}

if (is_callable($this->uploadProgressCallback)) {
call_user_func($this->uploadProgressCallback, $currStreamLimitSize);
}

$rangeStart = $this->getRangeStart($response->getHeaderLine('Range'));
} while ($response->getStatusCode() === 308);

return $this->decodeResponse($response);
}

/**
* Fetch and decode the response body
*
* @param ResponseInterface $response
* @return array
*/
protected function decodeResponse(ResponseInterface $response)
{
return $this->jsonDecode($response->getBody(), true);
}

Expand All @@ -183,33 +199,34 @@ public function upload()
*
* @return string
*/
private function createResumeUri()
protected function createResumeUri()
{
$headers = [
'X-Upload-Content-Type' => $this->contentType,
'X-Upload-Content-Length' => $this->data->getSize(),
'Content-Type' => 'application/json'
];

$body = $this->jsonEncode($this->metadata);

$request = new Request(
'POST',
$this->uri,
$headers,
$this->jsonEncode($this->metadata)
$body
);

$response = $this->requestWrapper->send($request, $this->requestOptions);
$this->resumeUri = $response->getHeaderLine('Location');

return $this->resumeUri;
return $this->resumeUri = $response->getHeaderLine('Location');
}

/**
* Gets the status of the upload.
*
* @return ResponseInterface
*/
private function getStatusResponse()
protected function getStatusResponse()
{
$request = new Request(
'PUT',
Expand All @@ -226,7 +243,7 @@ private function getStatusResponse()
* @param string $rangeHeader
* @return int
*/
private function getRangeStart($rangeHeader)
protected function getRangeStart($rangeHeader)
{
if (!$rangeHeader) {
return null;
Expand Down
61 changes: 61 additions & 0 deletions src/Core/Upload/SignedUrlUploader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* Copyright 2017 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\Core\Upload;

use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\ResponseInterface;

/**
* Upload data to Cloud Storage using a Signed URL
*/
class SignedUrlUploader extends ResumableUploader
{
/**
* Creates the resume URI.
*
* @return string
*/
protected function createResumeUri()
{
$headers = [
'Content-Type' => $this->contentType,
'Content-Length' => 0,
'x-goog-resumable' => 'start'
];

$request = new Request(
'POST',
$this->uri,
$headers
);

$response = $this->requestWrapper->send($request, $this->requestOptions);
return $this->resumeUri = $response->getHeaderLine('Location');
}

/**
* Decode the response body
*
* @param ReponseInterface $response
* @return string
*/
protected function decodeResponse(ResponseInterface $response)
{
return $response->getBody();
}
}
8 changes: 8 additions & 0 deletions src/Storage/Bucket.php
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,14 @@ public function upload($data, array $options = [])
* from the `encryptionKey` on your behalf if not provided, but
* for best performance it is recommended to pass in a cached
* version of the already calculated SHA.
* @type callable $uploadProgressCallback The given callable
* function/method will be called after each successfully uploaded
* chunk. The callable function/method will receive the number of
* uploaded bytes after each uploaded chunk as a parameter to this
* callable. It's useful if you want to create a progress bar when
* using resumable upload type together with $chunkSize parameter.
* If $chunkSize is not set the callable function/method will be
* called only once after the successful file upload.
* }
* @return ResumableUploader
* @throws \InvalidArgumentException
Expand Down
32 changes: 32 additions & 0 deletions src/Storage/EncryptionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
namespace Google\Cloud\Storage;

use InvalidArgumentException;
use phpseclib\Crypt\RSA;

/**
* Trait which provides helper methods for customer-supplied encryption.
Expand Down Expand Up @@ -109,4 +110,35 @@ private function buildHeaders($key, $keySHA256, $useCopySourceHeaders)

return [];
}

/**
* Sign a string using a given private key.
*
* @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(RSA::class) && !$forceOpenssl) {
$rsa = new RSA;
$rsa->loadKey($privateKey);
$rsa->setSignatureMode(RSA::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;
}
}
Loading