PluginProbe
Media Cloud Sync / 1.4.1
Media Cloud Sync v1.4.1
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 1.2.9 All 35 releases
media-cloud-sync / includes / sdk / s3 / Aws / S3 / Parser / S3Parser.php

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

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