Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
namespace Aws\Api\Serializer;
3
 
4
use Aws\Api\MapShape;
5
use Aws\Api\Service;
6
use Aws\Api\Operation;
7
use Aws\Api\Shape;
8
use Aws\Api\StructureShape;
9
use Aws\Api\TimestampShape;
10
use Aws\CommandInterface;
11
use Aws\EndpointV2\EndpointV2SerializerTrait;
1441 ariadna 12
use Aws\EndpointV2\Ruleset\RulesetEndpoint;
1 efrain 13
use GuzzleHttp\Psr7;
14
use GuzzleHttp\Psr7\Request;
15
use GuzzleHttp\Psr7\Uri;
16
use GuzzleHttp\Psr7\UriResolver;
17
use Psr\Http\Message\RequestInterface;
18
 
19
/**
20
 * Serializes HTTP locations like header, uri, payload, etc...
21
 * @internal
22
 */
23
abstract class RestSerializer
24
{
25
    use EndpointV2SerializerTrait;
26
 
27
    /** @var Service */
28
    private $api;
29
 
30
    /** @var Uri */
31
    private $endpoint;
32
 
33
    /**
34
     * @param Service $api      Service API description
35
     * @param string  $endpoint Endpoint to connect to
36
     */
37
    public function __construct(Service $api, $endpoint)
38
    {
39
        $this->api = $api;
40
        $this->endpoint = Psr7\Utils::uriFor($endpoint);
41
    }
42
 
43
    /**
44
     * @param CommandInterface $command Command to serialize into a request.
45
     * @param $clientArgs Client arguments used for dynamic endpoint resolution.
46
     *
47
     * @return RequestInterface
48
     */
49
    public function __invoke(
50
        CommandInterface $command,
1441 ariadna 51
        $endpoint = null
1 efrain 52
    )
53
    {
54
        $operation = $this->api->getOperation($command->getName());
55
        $commandArgs = $command->toArray();
56
        $opts = $this->serialize($operation, $commandArgs);
57
        $headers = isset($opts['headers']) ? $opts['headers'] : [];
58
 
1441 ariadna 59
        if ($endpoint instanceof RulesetEndpoint) {
60
            $this->setEndpointV2RequestOptions($endpoint, $headers);
1 efrain 61
        }
1441 ariadna 62
 
1 efrain 63
        $uri = $this->buildEndpoint($operation, $commandArgs, $opts);
64
 
65
        return new Request(
66
            $operation['http']['method'],
67
            $uri,
68
            $headers,
69
            isset($opts['body']) ? $opts['body'] : null
70
        );
71
    }
72
 
73
    /**
74
     * Modifies a hash of request options for a payload body.
75
     *
76
     * @param StructureShape   $member  Member to serialize
77
     * @param array            $value   Value to serialize
78
     * @param array            $opts    Request options to modify.
79
     */
80
    abstract protected function payload(
81
        StructureShape $member,
82
        array $value,
83
        array &$opts
84
    );
85
 
86
    private function serialize(Operation $operation, array $args)
87
    {
88
        $opts = [];
89
        $input = $operation->getInput();
90
 
91
        // Apply the payload trait if present
92
        if ($payload = $input['payload']) {
93
            $this->applyPayload($input, $payload, $args, $opts);
94
        }
95
 
96
        foreach ($args as $name => $value) {
97
            if ($input->hasMember($name)) {
98
                $member = $input->getMember($name);
99
                $location = $member['location'];
100
                if (!$payload && !$location) {
101
                    $bodyMembers[$name] = $value;
102
                } elseif ($location == 'header') {
103
                    $this->applyHeader($name, $member, $value, $opts);
104
                } elseif ($location == 'querystring') {
105
                    $this->applyQuery($name, $member, $value, $opts);
106
                } elseif ($location == 'headers') {
107
                    $this->applyHeaderMap($name, $member, $value, $opts);
108
                }
109
            }
110
        }
111
 
112
        if (isset($bodyMembers)) {
113
            $this->payload($operation->getInput(), $bodyMembers, $opts);
114
        } else if (!isset($opts['body']) && $this->hasPayloadParam($input, $payload)) {
115
            $this->payload($operation->getInput(), [], $opts);
116
        }
117
 
118
        return $opts;
119
    }
120
 
121
    private function applyPayload(StructureShape $input, $name, array $args, array &$opts)
122
    {
123
        if (!isset($args[$name])) {
124
            return;
125
        }
126
 
127
        $m = $input->getMember($name);
128
 
129
        if ($m['streaming'] ||
130
           ($m['type'] == 'string' || $m['type'] == 'blob')
131
        ) {
132
            // Streaming bodies or payloads that are strings are
133
            // always just a stream of data.
134
            $opts['body'] = Psr7\Utils::streamFor($args[$name]);
135
            return;
136
        }
137
 
138
        $this->payload($m, $args[$name], $opts);
139
    }
140
 
141
    private function applyHeader($name, Shape $member, $value, array &$opts)
142
    {
143
        if ($member->getType() === 'timestamp') {
144
            $timestampFormat = !empty($member['timestampFormat'])
145
                ? $member['timestampFormat']
146
                : 'rfc822';
147
            $value = TimestampShape::format($value, $timestampFormat);
148
        } elseif ($member->getType() === 'boolean') {
149
            $value = $value ? 'true' : 'false';
150
        }
151
 
152
        if ($member['jsonvalue']) {
153
            $value = json_encode($value);
154
            if (empty($value) && JSON_ERROR_NONE !== json_last_error()) {
155
                throw new \InvalidArgumentException('Unable to encode the provided value'
156
                    . ' with \'json_encode\'. ' . json_last_error_msg());
157
            }
158
 
159
            $value = base64_encode($value);
160
        }
161
 
162
        $opts['headers'][$member['locationName'] ?: $name] = $value;
163
    }
164
 
165
    /**
166
     * Note: This is currently only present in the Amazon S3 model.
167
     */
168
    private function applyHeaderMap($name, Shape $member, array $value, array &$opts)
169
    {
170
        $prefix = $member['locationName'];
171
        foreach ($value as $k => $v) {
172
            $opts['headers'][$prefix . $k] = $v;
173
        }
174
    }
175
 
176
    private function applyQuery($name, Shape $member, $value, array &$opts)
177
    {
178
        if ($member instanceof MapShape) {
179
            $opts['query'] = isset($opts['query']) && is_array($opts['query'])
180
                ? $opts['query'] + $value
181
                : $value;
182
        } elseif ($value !== null) {
183
            $type = $member->getType();
184
            if ($type === 'boolean') {
185
                $value = $value ? 'true' : 'false';
186
            } elseif ($type === 'timestamp') {
187
                $timestampFormat = !empty($member['timestampFormat'])
188
                    ? $member['timestampFormat']
189
                    : 'iso8601';
190
                $value = TimestampShape::format($value, $timestampFormat);
191
            }
192
 
193
            $opts['query'][$member['locationName'] ?: $name] = $value;
194
        }
195
    }
196
 
197
    private function buildEndpoint(Operation $operation, array $args, array $opts)
198
    {
1441 ariadna 199
        $isModifiedModel = $this->api->isModifiedModel();
200
        $serviceName = $this->api->getServiceName();
1 efrain 201
        // Create an associative array of variable definitions used in expansions
202
        $varDefinitions = $this->getVarDefinitions($operation, $args);
203
 
204
        $relative = preg_replace_callback(
205
            '/\{([^\}]+)\}/',
206
            function (array $matches) use ($varDefinitions) {
207
                $isGreedy = substr($matches[1], -1, 1) == '+';
208
                $k = $isGreedy ? substr($matches[1], 0, -1) : $matches[1];
209
                if (!isset($varDefinitions[$k])) {
210
                    return '';
211
                }
212
 
213
                if ($isGreedy) {
214
                    return str_replace('%2F', '/', rawurlencode($varDefinitions[$k]));
215
                }
216
 
217
                return rawurlencode($varDefinitions[$k]);
218
            },
219
            $operation['http']['requestUri']
220
        );
221
 
222
        // Add the query string variables or appending to one if needed.
223
        if (!empty($opts['query'])) {
224
           $relative = $this->appendQuery($opts['query'], $relative);
225
        }
226
 
227
        $path = $this->endpoint->getPath();
228
 
1441 ariadna 229
        if ($isModifiedModel && $serviceName === 's3') {
1 efrain 230
            if (substr($path, -1) === '/' && $relative[0] === '/') {
231
                $path = rtrim($path, '/');
232
            }
233
            $relative = $path . $relative;
1441 ariadna 234
 
235
            if (strpos($relative, '../') !== false
236
                || substr($relative, -2) === '..'
237
            ) {
238
                if ($relative[0] !== '/') {
239
                    $relative = '/' . $relative;
240
                }
241
 
242
                return new Uri($this->endpoint->withPath('') . $relative);
243
            }
1 efrain 244
        }
1441 ariadna 245
 
246
        if ((!empty($relative) && $relative !== '/')
247
            && !$isModifiedModel
248
            && $serviceName !== 's3'
249
        ) {
250
            $this->normalizePath($path);
251
        }
252
 
1 efrain 253
        // If endpoint has path, remove leading '/' to preserve URI resolution.
254
        if ($path && $relative[0] === '/') {
255
            $relative = substr($relative, 1);
256
        }
257
 
1441 ariadna 258
        //Append path to endpoint when leading '//...'
259
        // present as uri cannot be properly resolved
260
        if ($isModifiedModel && strpos($relative, '//') === 0) {
1 efrain 261
            return new Uri($this->endpoint . $relative);
262
        }
263
 
264
        // Expand path place holders using Amazon's slightly different URI
265
        // template syntax.
266
        return UriResolver::resolve($this->endpoint, new Uri($relative));
267
    }
268
 
269
    /**
270
     * @param StructureShape $input
271
     */
272
    private function hasPayloadParam(StructureShape $input, $payload)
273
    {
274
        if ($payload) {
275
            $potentiallyEmptyTypes = ['blob','string'];
276
            if ($this->api->getMetadata('protocol') == 'rest-xml') {
277
                $potentiallyEmptyTypes[] = 'structure';
278
            }
279
            $payloadMember = $input->getMember($payload);
280
            if (in_array($payloadMember['type'], $potentiallyEmptyTypes)) {
281
                return false;
282
            }
283
        }
284
        foreach ($input->getMembers() as $member) {
285
            if (!isset($member['location'])) {
286
                return true;
287
            }
288
        }
289
        return false;
290
    }
291
 
292
    private function appendQuery($query, $endpoint)
293
    {
294
        $append = Psr7\Query::build($query);
295
        return $endpoint .= strpos($endpoint, '?') !== false ? "&{$append}" : "?{$append}";
296
    }
297
 
298
    private function getVarDefinitions($command, $args)
299
    {
300
        $varDefinitions = [];
301
 
302
        foreach ($command->getInput()->getMembers() as $name => $member) {
303
            if ($member['location'] == 'uri') {
304
                $varDefinitions[$member['locationName'] ?: $name] =
305
                    isset($args[$name])
306
                        ? $args[$name]
307
                        : null;
308
            }
309
        }
310
        return $varDefinitions;
311
    }
1441 ariadna 312
 
313
    /**
314
     * Appends trailing slash to non-empty paths with at least one segment
315
     * to ensure proper URI resolution
316
     *
317
     * @param string $path
318
     *
319
     * @return void
320
     */
321
    private function normalizePath(string $path): void
322
    {
323
        if (!empty($path) && $path !== '/' && substr($path, -1) !== '/') {
324
            $this->endpoint = $this->endpoint->withPath($path . '/');
325
        }
326
    }
1 efrain 327
}