1<?php 2namespace Aws; 3 4use Aws\Signature\SignatureV4; 5use Aws\Endpoint\EndpointProvider; 6use GuzzleHttp\Psr7\Uri; 7use Psr\Http\Message\RequestInterface; 8 9/** 10 * @internal Adds computed values to service operations that need presigned url. 11 */ 12class PresignUrlMiddleware 13{ 14 private $client; 15 private $endpointProvider; 16 private $nextHandler; 17 /** @var array names of operations that require presign url */ 18 private $commandPool; 19 /** @var array query params that are not on the operation's model to add before signing */ 20 private $extraQueryParams; 21 /** @var string */ 22 private $serviceName; 23 /** @var string */ 24 private $presignParam; 25 /** @var bool */ 26 private $requireDifferentRegion; 27 28 public function __construct( 29 array $options, 30 callable $endpointProvider, 31 AwsClientInterface $client, 32 callable $nextHandler 33 ) { 34 $this->endpointProvider = $endpointProvider; 35 $this->client = $client; 36 $this->nextHandler = $nextHandler; 37 $this->commandPool = $options['operations']; 38 $this->serviceName = $options['service']; 39 $this->presignParam = !empty($options['presign_param']) 40 ? $options['presign_param'] 41 : 'PresignedUrl'; 42 $this->extraQueryParams = !empty($options['extra_query_params']) 43 ? $options['extra_query_params'] 44 : []; 45 $this->requireDifferentRegion = !empty($options['require_different_region']); 46 } 47 48 public static function wrap( 49 AwsClientInterface $client, 50 callable $endpointProvider, 51 array $options = [] 52 ) { 53 return function (callable $handler) use ($endpointProvider, $client, $options) { 54 $f = new PresignUrlMiddleware($options, $endpointProvider, $client, $handler); 55 return $f; 56 }; 57 } 58 59 public function __invoke(CommandInterface $cmd, RequestInterface $request = null) 60 { 61 if (in_array($cmd->getName(), $this->commandPool) 62 && (!isset($cmd->{'__skip' . $cmd->getName()})) 63 ) { 64 $cmd['DestinationRegion'] = $this->client->getRegion(); 65 if (!empty($cmd['SourceRegion']) && !empty($cmd[$this->presignParam])) { 66 goto nexthandler; 67 } 68 if (!$this->requireDifferentRegion 69 || (!empty($cmd['SourceRegion']) 70 && $cmd['SourceRegion'] !== $cmd['DestinationRegion']) 71 ) { 72 $cmd[$this->presignParam] = $this->createPresignedUrl($this->client, $cmd); 73 } 74 } 75 nexthandler: 76 $nextHandler = $this->nextHandler; 77 return $nextHandler($cmd, $request); 78 } 79 80 private function createPresignedUrl( 81 AwsClientInterface $client, 82 CommandInterface $cmd 83 ) { 84 $cmdName = $cmd->getName(); 85 $newCmd = $client->getCommand($cmdName, $cmd->toArray()); 86 // Avoid infinite recursion by flagging the new command. 87 $newCmd->{'__skip' . $cmdName} = true; 88 89 // Serialize a request for the operation. 90 $request = \Aws\serialize($newCmd); 91 // Create the new endpoint for the target endpoint. 92 $endpoint = EndpointProvider::resolve($this->endpointProvider, [ 93 'region' => $cmd['SourceRegion'], 94 'service' => $this->serviceName, 95 ])['endpoint']; 96 97 // Set the request to hit the target endpoint. 98 $uri = $request->getUri()->withHost((new Uri($endpoint))->getHost()); 99 $request = $request->withUri($uri); 100 // Create a presigned URL for our generated request. 101 $signer = new SignatureV4($this->serviceName, $cmd['SourceRegion']); 102 103 $currentQueryParams = (string) $request->getBody(); 104 $paramsToAdd = false; 105 if (!empty($this->extraQueryParams[$cmdName])) { 106 foreach ($this->extraQueryParams[$cmdName] as $param) { 107 if (!strpos($currentQueryParams, $param)) { 108 $paramsToAdd = "&{$param}={$cmd[$param]}"; 109 } 110 } 111 } 112 113 return (string) $signer->presign( 114 SignatureV4::convertPostToGet($request, $paramsToAdd ?: ""), 115 $client->getCredentials()->wait(), 116 '+1 hour' 117 )->getUri(); 118 } 119} 120