Issues (17)

lib/HttpClientBase.php (4 issues)

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
The assignment to $http_response_header is dead and can be removed.
Loading history...
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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
The assignment to $headerValue is dead and can be removed.
Loading history...
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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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