This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * Abstraction over Http client implementations. |
||
5 | * |
||
6 | * @author Maksim Masiukevich <[email protected]> |
||
7 | * @license MIT |
||
8 | * @license https://opensource.org/licenses/MIT |
||
9 | */ |
||
10 | |||
11 | declare(strict_types = 0); |
||
12 | |||
13 | namespace ServiceBus\HttpClient\Artax; |
||
14 | |||
15 | use Amp\Http\Client\Connection\ConnectionPool; |
||
16 | use Amp\Http\Client\Connection\UnlimitedConnectionPool; |
||
17 | use Amp\Http\Client\DelegateHttpClient; |
||
18 | use ServiceBus\HttpClient\Exception\HttpClientException; |
||
19 | use ServiceBus\HttpClient\RequestContext; |
||
20 | use function Amp\ByteStream\pipe; |
||
21 | use function Amp\call; |
||
22 | use function Amp\File\open; |
||
23 | use function Amp\File\rename; |
||
24 | use Amp\File\StatCache; |
||
25 | use Amp\Http\Client\HttpClientBuilder; |
||
26 | use Amp\Http\Client\Request; |
||
27 | use Amp\Http\Client\Response; |
||
28 | use Amp\Promise; |
||
29 | use Amp\TimeoutCancellationToken; |
||
30 | use GuzzleHttp\Psr7\Response as Psr7Response; |
||
31 | use Psr\Log\LoggerInterface; |
||
32 | use Psr\Log\NullLogger; |
||
33 | use ServiceBus\HttpClient\HttpClient; |
||
34 | use ServiceBus\HttpClient\HttpRequest; |
||
35 | |||
36 | /** |
||
37 | * Artax (amphp-based) http client. |
||
38 | */ |
||
39 | final class ArtaxHttpClient implements HttpClient |
||
40 | { |
||
41 | /** |
||
42 | * @var DelegateHttpClient |
||
43 | */ |
||
44 | private $handler; |
||
45 | |||
46 | /** |
||
47 | 4 | * @var LoggerInterface |
|
48 | */ |
||
49 | 4 | private $logger; |
|
50 | 4 | ||
51 | public function __construct(?ConnectionPool $connectionPool = null, LoggerInterface $logger = null) |
||
52 | 4 | { |
|
53 | 4 | $connectionPool = $connectionPool ?? new UnlimitedConnectionPool(); |
|
54 | $this->logger = $logger ?? new NullLogger(); |
||
55 | 3 | ||
56 | $this->handler = (new HttpClientBuilder())->usingPool($connectionPool)->build(); |
||
57 | 3 | } |
|
58 | |||
59 | 3 | public function execute(HttpRequest $requestData, ?RequestContext $context = null): Promise |
|
60 | 3 | { |
|
61 | $context = $context ?? new RequestContext(); |
||
62 | 3 | ||
63 | return call( |
||
64 | function () use ($requestData, $context): \Generator |
||
65 | 3 | { |
|
66 | $request = self::buildRequest($requestData, $context); |
||
67 | 1 | ||
68 | 3 | /** @var \GuzzleHttp\Psr7\Response $response */ |
|
69 | $response = yield from $this->doRequest($this->handler, $request, $context); |
||
70 | |||
71 | return $response; |
||
72 | 1 | } |
|
73 | ); |
||
74 | 1 | } |
|
75 | |||
76 | 1 | public function download(string $fileUrl, string $destinationDirectory, string $fileName, ?RequestContext $context = null): Promise |
|
77 | 1 | { |
|
78 | $context = $context ?? RequestContext::withoutLogging(); |
||
79 | |||
80 | return call( |
||
81 | 1 | function () use ($fileUrl, $destinationDirectory, $fileName, $context): \Generator |
|
82 | 1 | { |
|
83 | 1 | try |
|
84 | 1 | { |
|
85 | 1 | $request = new Request($fileUrl); |
|
86 | $request->setTransferTimeout($context->transferTimeout); |
||
87 | 1 | $request->setInactivityTimeout($context->inactivityTimeout); |
|
88 | $request->setTcpConnectTimeout($context->tcpConnectTimeout); |
||
89 | $request->setTlsHandshakeTimeout($context->tlsHandshakeTimeout); |
||
90 | |||
91 | if ($context->protocolVersion !== null) |
||
92 | { |
||
93 | 1 | $request->setProtocolVersions([$context->protocolVersion]); |
|
94 | 1 | } |
|
95 | 1 | ||
96 | /** @var Response $response */ |
||
97 | $response = yield $this->handler->request( |
||
98 | 1 | $request, |
|
99 | new TimeoutCancellationToken($context->transferTimeout) |
||
100 | ); |
||
101 | |||
102 | if ($response->getStatus() !== 200) |
||
103 | { |
||
104 | throw new HttpClientException( |
||
105 | \sprintf( |
||
106 | 'Failed to download file `%s`: incorrect server response code: %d', |
||
107 | $fileUrl, |
||
108 | $response->getStatus() |
||
109 | ) |
||
110 | 1 | ); |
|
111 | } |
||
112 | |||
113 | 1 | /** @var string $tmpDirectoryPath */ |
|
114 | $tmpDirectoryPath = \tempnam(\sys_get_temp_dir(), 'artax-streaming-'); |
||
115 | 1 | ||
116 | /** @var \Amp\File\File $tmpFile */ |
||
117 | 1 | $tmpFile = yield open($tmpDirectoryPath, 'w'); |
|
118 | 1 | ||
119 | 1 | yield pipe($response->getBody(), $tmpFile); |
|
120 | 1 | ||
121 | $destinationFilePath = \sprintf( |
||
122 | '%s/%s', |
||
123 | 1 | \rtrim($destinationDirectory, '/'), |
|
124 | 1 | \ltrim($fileName, '/') |
|
125 | ); |
||
126 | 1 | ||
127 | yield $tmpFile->close(); |
||
128 | 1 | yield rename($tmpDirectoryPath, $destinationFilePath); |
|
129 | |||
130 | StatCache::clear($tmpDirectoryPath); |
||
131 | |||
132 | return $destinationFilePath; |
||
133 | } |
||
134 | 1 | catch (\Throwable $throwable) |
|
135 | { |
||
136 | throw adaptArtaxThrowable($throwable); |
||
137 | } |
||
138 | 3 | } |
|
139 | ); |
||
140 | 3 | } |
|
141 | |||
142 | private function doRequest(DelegateHttpClient $client, Request $request, RequestContext $context): \Generator |
||
143 | { |
||
144 | 3 | $timeStart = \microtime(true); |
|
145 | |||
146 | 3 | try |
|
147 | { |
||
148 | if ($context->logRequest === true) |
||
149 | { |
||
150 | 3 | yield from logArtaxRequest($this->logger, $request, $context->traceId); |
|
151 | 3 | } |
|
152 | 3 | ||
153 | /** @var Response $artaxResponse */ |
||
154 | $artaxResponse = yield $client->request( |
||
155 | $request, |
||
156 | 1 | new TimeoutCancellationToken($context->transferTimeout) |
|
157 | ); |
||
158 | 1 | ||
159 | /** @var Psr7Response $response */ |
||
160 | 1 | $response = yield from self::adaptResponse($artaxResponse); |
|
161 | |||
162 | 1 | $executionTime = (string) (\microtime(true) - $timeStart); |
|
163 | |||
164 | if ($context->logResponse === true) |
||
165 | 1 | { |
|
166 | logArtaxResponse($this->logger, $response, $context->traceId, $executionTime); |
||
167 | 2 | } |
|
168 | |||
169 | 2 | return $response; |
|
170 | } |
||
171 | 2 | catch (\Throwable $throwable) |
|
172 | { |
||
173 | 2 | $executionTime = (string) (\microtime(true) - $timeStart); |
|
174 | |||
175 | logArtaxThrowable($this->logger, $throwable, $context->traceId, $executionTime); |
||
176 | |||
177 | 3 | throw adaptArtaxThrowable($throwable); |
|
178 | } |
||
179 | 3 | } |
|
180 | |||
181 | 3 | private static function buildRequest(HttpRequest $requestData, RequestContext $context): Request |
|
182 | 3 | { |
|
183 | 3 | $request = new Request($requestData->url, $requestData->method); |
|
184 | 3 | ||
185 | $request->setTransferTimeout($context->transferTimeout); |
||
186 | $request->setInactivityTimeout($context->inactivityTimeout); |
||
187 | $request->setTcpConnectTimeout($context->tcpConnectTimeout); |
||
188 | $request->setTlsHandshakeTimeout($context->tlsHandshakeTimeout); |
||
189 | |||
190 | 3 | /** |
|
191 | * @var string $headerKey |
||
192 | * @var string|string[] $value |
||
193 | */ |
||
194 | foreach ($requestData->headers as $headerKey => $value) |
||
195 | { |
||
196 | 3 | $request->setHeader($headerKey, $value); |
|
197 | } |
||
198 | 3 | ||
199 | 3 | /** @var ArtaxFormBody|string|null $body */ |
|
200 | $body = $requestData->body; |
||
201 | 3 | ||
202 | $request->setBody( |
||
203 | $body instanceof ArtaxFormBody |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
204 | 3 | ? $body->preparedBody() |
|
205 | : $body |
||
206 | ); |
||
207 | 1 | ||
208 | return $request; |
||
209 | 1 | } |
|
210 | |||
211 | 1 | private static function adaptResponse(Response $response): \Generator |
|
212 | 1 | { |
|
213 | 1 | $responseBody = yield $response->getBody()->buffer(); |
|
214 | |||
215 | 1 | return new Psr7Response( |
|
216 | 1 | $response->getStatus(), |
|
217 | $response->getHeaders(), |
||
218 | $responseBody, |
||
219 | $response->getProtocolVersion(), |
||
220 | $response->getReason() |
||
221 | ); |
||
222 | } |
||
223 | } |
||
224 |