Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
 
3
namespace Aws\S3\Parser;
4
 
5
use Aws\Api\ErrorParser\XmlErrorParser;
6
use Aws\Api\Parser\AbstractParser;
7
use Aws\Api\Parser\Exception\ParserException;
8
use Aws\Api\Service;
9
use Aws\Api\StructureShape;
10
use Aws\CommandInterface;
11
use Aws\Exception\AwsException;
12
use Aws\ResultInterface;
13
use GuzzleHttp\Psr7\Utils;
14
use Psr\Http\Message\ResponseInterface;
15
use Psr\Http\Message\StreamInterface;
16
 
17
/**
18
 * Custom S3 parser on top of the S3 protocol parser
19
 * for handling specific S3 parsing scenarios.
20
 *
21
 * @internal
22
 */
23
final class S3Parser extends AbstractParser
24
{
25
     /** @var AbstractParser */
26
    private $protocolParser;
27
    /**  @var XmlErrorParser */
28
    private $errorParser;
29
    /** @var string */
30
    private $exceptionClass;
31
    /** @var array */
32
    private $s3ResultMutators;
33
 
34
    /**
35
     * @param AbstractParser $protocolParser
36
     * @param XmlErrorParser $errorParser
37
     * @param Service $api
38
     * @param string $exceptionClass
39
     */
40
    public function __construct(
41
        AbstractParser $protocolParser,
42
        XmlErrorParser $errorParser,
43
        Service $api,
44
        string $exceptionClass = AwsException::class
45
    )
46
    {
47
        parent::__construct($api);
48
        $this->protocolParser = $protocolParser;
49
        $this->errorParser = $errorParser;
50
        $this->exceptionClass = $exceptionClass;
51
        $this->s3ResultMutators = [];
52
    }
53
 
54
    /**
55
     * Parses a S3 response.
56
     *
57
     * @param CommandInterface $command The command that originated the request.
58
     * @param ResponseInterface $response The response received from the service.
59
     *
60
     * @return ResultInterface|null
61
     */
62
    public function __invoke(
63
        CommandInterface $command,
64
        ResponseInterface $response
65
    ):? ResultInterface
66
    {
67
        // Check first if the response is an error
68
        $this->parse200Error($command, $response);
69
 
70
        try {
71
            $parseFn = $this->protocolParser;
72
            $result = $parseFn($command, $response);
73
        } catch (ParserException $e) {
74
            // Parsing errors will be considered retryable.
75
            throw new $this->exceptionClass(
76
                "Error parsing response for {$command->getName()}:"
77
                . " AWS parsing error: {$e->getMessage()}",
78
                $command,
79
                ['connection_error' => true, 'exception' => $e],
80
                $e
81
            );
82
        }
83
 
84
        return $this->executeS3ResultMutators($result, $command, $response);
85
    }
86
 
87
    /**
88
     * Tries to parse a 200 response as an error from S3.
89
     * If the parsed result contains a code and message then that means an error
90
     * was found, and hence an exception is thrown with that error.
91
     *
92
     * @param CommandInterface $command
93
     * @param ResponseInterface $response
94
     *
95
     * @return void
96
     */
97
    private function parse200Error(
98
        CommandInterface $command,
99
        ResponseInterface $response
100
    ): void
101
    {
102
        // This error parsing should be just for 200 error responses
103
        // and operations where its output shape does not have a streaming
104
        // member.
105
        if (200 !== $response->getStatusCode()
106
            || !$this->shouldBeConsidered200Error($command->getName())) {
107
            return;
108
        }
109
 
110
        // To guarantee we try the error parsing just for an Error xml response.
111
        if (!$this->isFirstRootElementError($response->getBody())) {
112
            return;
113
        }
114
 
115
        try {
116
            $errorParserFn = $this->errorParser;
117
            $parsedError = $errorParserFn($response, $command);
118
        } catch (ParserException $e) {
119
            // Parsing errors will be considered retryable.
120
            $parsedError = [
121
                'code' => 'ConnectionError',
122
                'message' => "An error connecting to the service occurred"
123
                    . " while performing the " . $command->getName()
124
                    . " operation."
125
            ];
126
        }
127
 
128
        if (isset($parsedError['code']) && isset($parsedError['message'])) {
129
            throw new $this->exceptionClass(
130
                $parsedError['message'],
131
                $command,
132
                [
133
                    'connection_error' => true,
134
                    'code' => $parsedError['code'],
135
                    'message' => $parsedError['message']
136
                ]
137
            );
138
        }
139
    }
140
 
141
    /**
142
     * Checks if a specific operation should be considered
143
     * a s3 200 error. Operations where any of its output members
144
     * has a streaming or httpPayload trait should be not considered.
145
     *
146
     * @param $commandName
147
     *
148
     * @return bool
149
     */
150
    private function shouldBeConsidered200Error($commandName): bool
151
    {
152
        $operation = $this->api->getOperation($commandName);
153
        $output = $operation->getOutput();
154
        foreach ($output->getMembers() as $_ => $memberProps) {
155
            if (!empty($memberProps['eventstream']) || !empty($memberProps['streaming'])) {
156
                return false;
157
            }
158
        }
159
 
160
        return true;
161
    }
162
 
163
    /**
164
     * Checks if the root element of the response body is "Error", which is
165
     * when we should try to parse an error from a 200 response from s3.
166
     * It is recommended to make sure the stream given is seekable, otherwise
167
     * the rewind call will cause a user warning.
168
     *
169
     * @param StreamInterface $responseBody
170
     *
171
     * @return bool
172
     */
173
    private function isFirstRootElementError(StreamInterface $responseBody): bool
174
    {
175
        static $pattern = '/<\?xml version="1\.0" encoding="UTF-8"\?>\s*<Error>/';
176
        // To avoid performance overhead in large streams
177
        $reducedBodyContent = $responseBody->read(64);
178
        $foundErrorElement = preg_match($pattern, $reducedBodyContent);
179
        // A rewind is needed because the stream is partially or entirely consumed
180
        // in the previous read operation.
181
        $responseBody->rewind();
182
 
183
        return $foundErrorElement;
184
    }
185
 
186
    /**
187
     * Execute mutator implementations over a result.
188
     * Mutators are logics that modifies a result.
189
     *
190
     * @param ResultInterface $result
191
     * @param CommandInterface $command
192
     * @param ResponseInterface $response
193
     *
194
     * @return ResultInterface
195
     */
196
    private function executeS3ResultMutators(
197
        ResultInterface $result,
198
        CommandInterface $command,
199
        ResponseInterface $response
200
    ): ResultInterface
201
    {
202
        foreach ($this->s3ResultMutators as $mutator) {
203
            $result = $mutator($result, $command, $response);
204
        }
205
 
206
        return $result;
207
    }
208
 
209
    /**
210
     * Adds a mutator into the list of mutators.
211
     *
212
     * @param string $mutatorName
213
     * @param S3ResultMutator $s3ResultMutator
214
     * @return void
215
     */
216
    public function addS3ResultMutator(
217
        string $mutatorName,
218
        S3ResultMutator $s3ResultMutator
219
    ): void
220
    {
221
        if (isset($this->s3ResultMutators[$mutatorName])) {
222
            trigger_error(
223
                "The S3 Result Mutator {$mutatorName} already exists!",
224
                E_USER_WARNING
225
            );
226
 
227
            return;
228
        }
229
 
230
        $this->s3ResultMutators[$mutatorName] = $s3ResultMutator;
231
    }
232
 
233
    /**
234
     * Removes a mutator from the mutator list.
235
     *
236
     * @param string $mutatorName
237
     * @return void
238
     */
239
    public function removeS3ResultMutator(string $mutatorName): void
240
    {
241
        if (!isset($this->s3ResultMutators[$mutatorName])) {
242
            trigger_error(
243
                "The S3 Result Mutator {$mutatorName} does not exist!",
244
                E_USER_WARNING
245
            );
246
 
247
            return;
248
        }
249
 
250
        unset($this->s3ResultMutators[$mutatorName]);
251
    }
252
 
253
    /**
254
     * Returns the list of result mutators available.
255
     *
256
     * @return array
257
     */
258
    public function getS3ResultMutators(): array
259
    {
260
        return $this->s3ResultMutators;
261
    }
262
 
263
    public function parseMemberFromStream(
264
        StreamInterface $stream,
265
        StructureShape $member,
266
        $response
267
    )
268
    {
269
        return $this->protocolParser->parseMemberFromStream(
270
            $stream,
271
            $member,
272
            $response
273
        );
274
    }
275
}