PluginProbe
Media Cloud Sync / 1.2.13
Media Cloud Sync v1.2.13
1.4.2 1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 All 36 releases
media-cloud-sync / includes / sdk / s3 / Aws / S3 / Parser / S3Parser.php

S3Parser.php in Media Cloud Sync 1.2.13, at includes/sdk/s3/Aws/S3/Parser/S3Parser.php

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