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