hasCache($document)) { $response = $this->getResponseFromCache($document); } else { try { $response = $this ->client ->request( Request::METHOD_HEAD, $this ->tempUrlGenerator ->generate( Request::METHOD_HEAD, $document->getFilename() ) ->url ); } catch (TransportExceptionInterface $exception) { throw StoredObjectManagerException::errorDuringHttpRequest($exception); } } return $this->extractLastModifiedFromResponse($response); } public function getContentLength(StoredObject $document): int { if ([] === $document->getKeyInfos()) { if ($this->hasCache($document)) { $response = $this->getResponseFromCache($document); } else { try { $response = $this ->client ->request( Request::METHOD_HEAD, $this ->tempUrlGenerator ->generate( Request::METHOD_HEAD, $document->getFilename() ) ->url ); } catch (TransportExceptionInterface $exception) { throw StoredObjectManagerException::errorDuringHttpRequest($exception); } } return $this->extractContentLengthFromResponse($response); } return strlen($this->read($document)); } public function etag(StoredObject $document): string { if ($this->hasCache($document)) { $response = $this->getResponseFromCache($document); } else { try { $response = $this ->client ->request( Request::METHOD_HEAD, $this ->tempUrlGenerator ->generate( Request::METHOD_HEAD, $document->getFilename() ) ->url ); } catch (TransportExceptionInterface $exception) { throw StoredObjectManagerException::errorDuringHttpRequest($exception); } } return $this->extractEtagFromResponse($response, $document); } public function read(StoredObject $document): string { $response = $this->getResponseFromCache($document); try { $data = $response->getContent(); } catch (\Throwable $e) { throw StoredObjectManagerException::unableToGetResponseContent($e); } if (false === $this->hasKeysAndIv($document)) { return $data; } $clearData = openssl_decrypt( $data, self::ALGORITHM, // TODO: Why using this library and not use base64_decode() ? Base64Url::decode($document->getKeyInfos()['k']), \OPENSSL_RAW_DATA, pack('C*', ...$document->getIv()) ); if (false === $clearData) { throw StoredObjectManagerException::unableToDecrypt(openssl_error_string()); } return $clearData; } public function write(StoredObject $document, string $clearContent): void { if ($this->hasCache($document)) { unset($this->inMemory[$document->getUuid()->toString()]); } $encryptedContent = $this->hasKeysAndIv($document) ? openssl_encrypt( $clearContent, self::ALGORITHM, // TODO: Why using this library and not use base64_decode() ? Base64Url::decode($document->getKeyInfos()['k']), \OPENSSL_RAW_DATA, pack('C*', ...$document->getIv()) ) : $clearContent; $headers = []; if (null !== $document->getDeleteAt()) { $headers['X-Delete-At'] = $document->getDeleteAt()->getTimestamp(); } try { $response = $this ->client ->request( Request::METHOD_PUT, $this ->tempUrlGenerator ->generate( Request::METHOD_PUT, $document->getFilename() ) ->url, [ 'body' => $encryptedContent, 'headers' => $headers, ] ); } catch (TransportExceptionInterface $exception) { throw StoredObjectManagerException::errorDuringHttpRequest($exception); } if (Response::HTTP_CREATED !== $response->getStatusCode()) { throw StoredObjectManagerException::invalidStatusCode($response->getStatusCode()); } } public function clearCache(): void { $this->inMemory = []; } private function extractLastModifiedFromResponse(ResponseInterface $response): \DateTimeImmutable { $lastModifiedString = (($response->getHeaders()['last-modified'] ?? [])[0] ?? ''); $date = \DateTimeImmutable::createFromFormat( \DateTimeImmutable::RFC7231, $lastModifiedString, new \DateTimeZone('GMT') ); if (false === $date) { throw new \RuntimeException('the date from remote storage could not be parsed: '.$lastModifiedString); } return $date; } private function extractContentLengthFromResponse(ResponseInterface $response): int { return (int) ($response->getHeaders()['content-length'] ?? ['0'])[0]; } private function extractEtagFromResponse(ResponseInterface $response, StoredObject $storedObject): ?string { $etag = ($response->getHeaders()['etag'] ?? [''])[0]; if ('' === $etag) { return null; } return $etag; } private function fillCache(StoredObject $document): void { try { $response = $this ->client ->request( Request::METHOD_GET, $this ->tempUrlGenerator ->generate( Request::METHOD_GET, $document->getFilename() ) ->url ); } catch (\Throwable $e) { throw StoredObjectManagerException::errorDuringHttpRequest($e); } if (Response::HTTP_OK !== $response->getStatusCode()) { throw StoredObjectManagerException::invalidStatusCode($response->getStatusCode()); } $this->inMemory[$document->getUuid()->toString()] = $response; } private function getResponseFromCache(StoredObject $document): ResponseInterface { if (!$this->hasCache($document)) { $this->fillCache($document); } return $this->inMemory[$document->getUuid()->toString()]; } private function hasCache(StoredObject $document): bool { return \array_key_exists($document->getUuid()->toString(), $this->inMemory); } private function hasKeysAndIv(StoredObject $storedObject): bool { return ([] !== $storedObject->getKeyInfos()) && ([] !== $storedObject->getIv()); } }