1 | <?php |
||
2 | /* |
||
3 | * The MIT License |
||
4 | * |
||
5 | * Copyright 2016 BCL Technologies. |
||
6 | * |
||
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
||
8 | * of this software and associated documentation files (the "Software"), to deal |
||
9 | * in the Software without restriction, including without limitation the rights |
||
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||
11 | * copies of the Software, and to permit persons to whom the Software is |
||
12 | * furnished to do so, subject to the following conditions: |
||
13 | * |
||
14 | * The above copyright notice and this permission notice shall be included in |
||
15 | * all copies or substantial portions of the Software. |
||
16 | * |
||
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||
23 | * THE SOFTWARE. |
||
24 | */ |
||
25 | |||
26 | namespace Bcl\EasyPdfCloud; |
||
27 | |||
28 | use function mb_strpos; |
||
29 | use function mb_strlen; |
||
30 | use function mb_substr; |
||
31 | use function mb_strtolower; |
||
32 | use function file_get_contents; |
||
33 | use function array_map; |
||
34 | use function count; |
||
35 | use function explode; |
||
36 | use function trim; |
||
37 | use function urldecode; |
||
38 | use function base64_decode; |
||
39 | use function json_decode; |
||
40 | |||
41 | class HttpClientBase |
||
42 | { |
||
43 | const CRLF = "\r\n"; |
||
44 | |||
45 | const HTTP_OK = 200; |
||
46 | const HTTP_ACCEPTED = 202; |
||
47 | const HTTP_MULTIPLE_CHOICES = 300; |
||
48 | const HTTP_BAD_REQUEST = 400; |
||
49 | const HTTP_UNAUTHORIZED = 401; |
||
50 | |||
51 | protected function stringStartsWith($source, $subString) |
||
52 | { |
||
53 | return 0 === mb_strpos($source, $subString, 0, Constraints::UTF_8); |
||
54 | } |
||
55 | |||
56 | protected function stringEndsWith($source, $subString) |
||
57 | { |
||
58 | $sourceLength = StringUtils::length($source); |
||
59 | $subStringLength = StringUtils::length($subString); |
||
60 | $subStringIndex = $sourceLength - $subStringLength; |
||
61 | |||
62 | if ($subStringLength < 0) { |
||
63 | return false; |
||
64 | } |
||
65 | |||
66 | $newString = (mb_substr($source, $subStringIndex, $subStringLength, Constraints::UTF_8)); |
||
67 | |||
68 | return $newString === $subString; |
||
69 | } |
||
70 | |||
71 | protected function getValueFromArray(array $array, $index, $defaultValue = null) |
||
72 | { |
||
73 | return isset($array[$index]) ? $array[$index] : $defaultValue; |
||
74 | } |
||
75 | |||
76 | protected function getHttpResponseFromUrl($url, $context) |
||
77 | { |
||
78 | $http_response_header = null; |
||
0 ignored issues
–
show
Unused Code
introduced
by
![]() |
|||
79 | |||
80 | $contents = @file_get_contents($url, false, $context); |
||
81 | if (false === $contents) { |
||
82 | throw new EasyPdfCloudApiException(0, 'Unable to communicate to the server'); |
||
0 ignored issues
–
show
The type
Bcl\EasyPdfCloud\EasyPdfCloudApiException was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||
83 | } |
||
84 | |||
85 | return array( |
||
86 | 'header' => $http_response_header, |
||
87 | 'contents' => $contents, |
||
88 | ); |
||
89 | } |
||
90 | |||
91 | protected function mapFirstLineHeaders($header) |
||
92 | { |
||
93 | $array = array(); |
||
94 | |||
95 | $keyValueMap = array_map('trim', explode(':', $header, 2)); |
||
96 | $headerName = (count($keyValueMap) >= 1 ? $keyValueMap[0] : null); |
||
97 | $headerValue = (count($keyValueMap) >= 2 ? $keyValueMap[1] : null); |
||
0 ignored issues
–
show
|
|||
98 | |||
99 | $firstLineMap = array_map('trim', explode(' ', $headerName, 3)); |
||
100 | |||
101 | if (count($firstLineMap) >= 1) { |
||
102 | $httpVersionHeader = $firstLineMap[0]; |
||
103 | $httpVersionMap = array_map('trim', explode('/', $httpVersionHeader, 2)); |
||
104 | |||
105 | if (count($httpVersionMap) >= 2) { |
||
106 | $headerNameLC = 'http-version'; |
||
107 | $headerValue = $httpVersionMap[1]; |
||
108 | $array += array($headerNameLC => $headerValue); |
||
109 | } |
||
110 | |||
111 | if (count($firstLineMap) >= 2) { |
||
112 | $headerNameLC = 'status-code'; |
||
113 | $headerValue = $firstLineMap[1]; |
||
114 | $array += array($headerNameLC => $headerValue); |
||
115 | |||
116 | if (count($firstLineMap) >= 3) { |
||
117 | $headerNameLC = 'status-description'; |
||
118 | $headerValue = $firstLineMap[2]; |
||
119 | $array += array($headerNameLC => $headerValue); |
||
120 | } |
||
121 | } |
||
122 | } |
||
123 | |||
124 | return $array; |
||
125 | } |
||
126 | |||
127 | protected function mapHttpHeaders($headers) |
||
128 | { |
||
129 | $headersCount = count($headers); |
||
130 | if (0 === $headersCount) { |
||
131 | return array(); |
||
132 | } |
||
133 | |||
134 | $header = $headers[0]; |
||
135 | $array = $this->mapFirstLineHeaders($header); |
||
136 | |||
137 | for ($i = 1; $i < $headersCount; ++$i) { |
||
138 | $header = $headers[$i]; |
||
139 | |||
140 | $keyValueMap = array_map('trim', explode(':', $header, 2)); |
||
141 | $headerName = (count($keyValueMap) >= 1 ? $keyValueMap[0] : null); |
||
142 | $headerValue = (count($keyValueMap) >= 2 ? $keyValueMap[1] : null); |
||
143 | |||
144 | if (null !== $headerName) { |
||
145 | $headerNameLC = mb_strtolower($headerName, Constraints::UTF_8); |
||
146 | $array += array($headerNameLC => $headerValue); |
||
147 | } |
||
148 | } |
||
149 | |||
150 | return $array; |
||
151 | } |
||
152 | |||
153 | protected function getStatusCodeFromResponse($headers) |
||
154 | { |
||
155 | if (!isset($headers['status-code'])) { |
||
156 | return 0; |
||
157 | } |
||
158 | |||
159 | $statusCode = (int) $headers['status-code']; |
||
160 | |||
161 | return $statusCode; |
||
162 | } |
||
163 | |||
164 | protected function isSuccessfulResponse($headers) |
||
165 | { |
||
166 | $statusCode = $this->getStatusCodeFromResponse($headers); |
||
167 | |||
168 | return $statusCode >= self::HTTP_OK && $statusCode < self::HTTP_MULTIPLE_CHOICES; |
||
169 | } |
||
170 | |||
171 | protected function getFileNameFromContentDisposisionHeader($contentDisposition) |
||
172 | { |
||
173 | if (!isset($contentDisposition)) { |
||
174 | return null; |
||
175 | } |
||
176 | |||
177 | $colonSeparatedList = array_map('trim', explode(';', $contentDisposition)); |
||
178 | $array = array(); |
||
179 | |||
180 | foreach ($colonSeparatedList as $item) { |
||
181 | $separatedList = array_map('trim', explode('=', $item, 2)); |
||
182 | |||
183 | if (count($separatedList) >= 2) { |
||
184 | $nameLC = mb_strtolower($separatedList[0], Constraints::UTF_8); |
||
185 | $value = trim($separatedList[1], '"'); |
||
186 | |||
187 | $array += array($nameLC => $value); |
||
188 | } |
||
189 | } |
||
190 | |||
191 | if (isset($array['filename*'])) { |
||
192 | $fileName = $array['filename*']; |
||
193 | |||
194 | $utf8Prefix = 'utf-8\'\''; |
||
195 | |||
196 | if ($this->stringStartsWith($fileName, $utf8Prefix)) { |
||
197 | // trim utf8 prefix |
||
198 | $prefixLength = mb_strlen($utf8Prefix, Constraints::UTF_8); |
||
199 | $fileName = mb_substr($fileName, $prefixLength, null, Constraints::UTF_8); |
||
200 | } |
||
201 | |||
202 | // URL decode and return |
||
203 | $fileName = urldecode($fileName); |
||
204 | |||
205 | return $fileName; |
||
206 | } elseif (isset($array['filename'])) { |
||
207 | $fileName = $array['filename']; |
||
208 | |||
209 | $base64Prefix = '=?utf-8?B?'; |
||
210 | $base64Postfix = '?='; |
||
211 | |||
212 | if ($this->stringStartsWith($fileName, $base64Prefix)) { |
||
213 | // trim base64 prefix |
||
214 | $prefixLength = mb_strlen($base64Prefix, Constraints::UTF_8); |
||
215 | $fileName = mb_substr($fileName, $prefixLength, null, Constraints::UTF_8); |
||
216 | |||
217 | if ($this->stringEndsWith($fileName, $base64Postfix)) { |
||
218 | // trim base64 postfix |
||
219 | $fileNameLength = mb_strlen($fileName, Constraints::UTF_8); |
||
220 | $postfixLength = mb_strlen($base64Postfix, Constraints::UTF_8); |
||
221 | $substrLength = $fileNameLength - $postfixLength; |
||
222 | $fileName = mb_substr($fileName, 0, $substrLength, Constraints::UTF_8); |
||
223 | } |
||
224 | |||
225 | // base64 decode and return |
||
226 | $fileName = base64_decode($fileName, true); |
||
227 | |||
228 | return $fileName; |
||
229 | } |
||
230 | |||
231 | // URL decode and return |
||
232 | $fileName = urldecode($fileName); |
||
233 | |||
234 | return $fileName; |
||
235 | } |
||
236 | |||
237 | return null; |
||
238 | } |
||
239 | |||
240 | protected function parseWwwAuthenticateResonseHeader($wwwAuthenticate) |
||
241 | { |
||
242 | $spaceSeparatedList = array_map('trim', explode(' ', $wwwAuthenticate)); |
||
243 | $commaSeparatedList = array(); |
||
244 | $array = array(); |
||
245 | |||
246 | foreach ($spaceSeparatedList as $item) { |
||
247 | $separatedList = array_map('trim', explode(',', $item)); |
||
248 | |||
249 | if (count($separatedList) >= 2) { |
||
250 | $commaSeparatedList = array_merge($commaSeparatedList, $separatedList); |
||
251 | } |
||
252 | } |
||
253 | |||
254 | foreach ($commaSeparatedList as $item) { |
||
255 | $separatedList = array_map('trim', explode('=', $item, 2)); |
||
256 | |||
257 | if (count($separatedList) >= 2) { |
||
258 | $nameLC = mb_strtolower(trim($separatedList[0]), Constraints::UTF_8); |
||
259 | $value = urldecode(trim(trim($separatedList[1]), '"')); |
||
260 | |||
261 | $array += array($nameLC => $value); |
||
262 | } |
||
263 | } |
||
264 | |||
265 | return $array; |
||
266 | } |
||
267 | |||
268 | protected function checkWwwAuthenticateResponseHeader($headers) |
||
269 | { |
||
270 | $statusCode = $this->getStatusCodeFromResponse($headers); |
||
271 | |||
272 | if (self::HTTP_BAD_REQUEST !== $statusCode && self::HTTP_UNAUTHORIZED !== $statusCode) { |
||
273 | return; |
||
274 | } |
||
275 | |||
276 | if (!isset($headers['www-authenticate'])) { |
||
277 | return; |
||
278 | } |
||
279 | |||
280 | $wwwAuthenticate = $headers['www-authenticate']; |
||
281 | $wwwAuthenticateMap = $this->parseWwwAuthenticateResonseHeader($wwwAuthenticate); |
||
282 | |||
283 | if (isset($wwwAuthenticateMap['error'])) { |
||
284 | $error = $wwwAuthenticateMap['error']; |
||
285 | |||
286 | $errorDescription = ''; |
||
287 | if (isset($wwwAuthenticateMap['error_description'])) { |
||
288 | $errorDescription = $wwwAuthenticateMap['error_description']; |
||
289 | } |
||
290 | |||
291 | throw new ApiAuthorizationException($statusCode, $error, $errorDescription); |
||
0 ignored issues
–
show
The type
Bcl\EasyPdfCloud\ApiAuthorizationException was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||
292 | } |
||
293 | } |
||
294 | |||
295 | protected function needsToRefreshToken($headers) |
||
296 | { |
||
297 | try { |
||
298 | $this->checkWwwAuthenticateResponseHeader($headers); |
||
299 | } catch (ApiAuthorizationException $e) { |
||
300 | $error = $e->getError(); |
||
301 | if ('invalid_token' === $error || 'expired_token' === $error) { |
||
302 | return true; |
||
303 | } |
||
304 | |||
305 | throw $e; |
||
306 | } |
||
307 | |||
308 | return false; |
||
309 | } |
||
310 | |||
311 | protected function decodeJsonFromResponse($headers, $contents, $failIfNotJson) |
||
312 | { |
||
313 | if (!isset($headers['content-type'])) { |
||
314 | if ($failIfNotJson) { |
||
315 | throw new EasyPdfCloudApiException(0, 'Unsupported response data format (only JSON is supported)'); |
||
316 | } |
||
317 | |||
318 | return array(); // return empty array |
||
319 | } |
||
320 | |||
321 | $contentType = $headers['content-type']; |
||
322 | |||
323 | if (!$this->stringStartsWith($contentType, 'application/json')) { |
||
324 | if ($failIfNotJson) { |
||
325 | throw new EasyPdfCloudApiException(0, 'Unsupported response data format (only JSON is supported)'); |
||
326 | } |
||
327 | |||
328 | return array(); // return empty array |
||
329 | } |
||
330 | |||
331 | if (!isset($contents)) { |
||
332 | return array(); // return empty array |
||
333 | } |
||
334 | |||
335 | $jsonResponse = json_decode($contents, true); |
||
336 | if (!isset($jsonResponse)) { |
||
337 | return array(); // return empty array |
||
338 | } |
||
339 | |||
340 | return $jsonResponse; |
||
341 | } |
||
342 | |||
343 | protected function handleResponse($headers, $contents = null) |
||
344 | { |
||
345 | if ($this->isSuccessfulResponse($headers)) { |
||
346 | // successful |
||
347 | return true; |
||
348 | } elseif ($this->needsToRefreshToken($headers)) { |
||
349 | // need to refresh token & try again |
||
350 | return false; |
||
351 | } |
||
352 | |||
353 | // failed |
||
354 | |||
355 | $statusCode = $this->getStatusCodeFromResponse($headers); |
||
356 | |||
357 | $statusDescription = 'Unknown error occurred'; |
||
358 | if (isset($headers['status-description'])) { |
||
359 | $statusDescription = $headers['status-description']; |
||
360 | } |
||
361 | |||
362 | $jsonResponse = $this->decodeJsonFromResponse($headers, $contents, false); |
||
363 | |||
364 | if (isset($jsonResponse['error'])) { |
||
365 | $error = $jsonResponse['error']; |
||
366 | throw new EasyPdfCloudApiException($statusCode, $statusDescription, $error); |
||
367 | } elseif (isset($jsonResponse['message'])) { |
||
368 | $message = $jsonResponse['message']; |
||
369 | throw new EasyPdfCloudApiException($statusCode, $statusDescription, $message); |
||
370 | } |
||
371 | |||
372 | throw new EasyPdfCloudApiException($statusCode, $statusDescription); |
||
373 | } |
||
374 | } |
||
375 |