Issues (141)

src/HttpRequestCurl.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource;
6
7
use CurlHandle;
8
use Override;
9
use RuntimeException;
10
11
use function assert;
12
use function count;
13
use function curl_exec;
14
use function curl_getinfo;
15
use function curl_init;
16
use function curl_setopt;
17
use function explode;
18
use function http_build_query;
19
use function json_decode;
20
use function str_contains;
21
use function strtolower;
22
use function substr;
23
use function trim;
24
25
use const CURLINFO_CONTENT_TYPE;
26
use const CURLINFO_HEADER_SIZE;
27
use const CURLINFO_HTTP_CODE;
28
use const CURLOPT_CUSTOMREQUEST;
29
use const CURLOPT_HEADER;
30
use const CURLOPT_HTTPHEADER;
31
use const CURLOPT_POSTFIELDS;
32
use const CURLOPT_RETURNTRANSFER;
33
use const CURLOPT_URL;
34
35
/**
36
 * Sends a HTTP request using cURL
37
 *
38
 * @psalm-import-type Query from Types
39
 * @psalm-import-type HttpHeaders from Types
40
 * @psalm-import-type HttpBody from Types
41
 * @psalm-import-type RequestOptions from Types
42
 */
43
final readonly class HttpRequestCurl implements HttpRequestInterface
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_READONLY, expecting T_CLASS on line 43 at column 6
Loading history...
44
{
45
    public function __construct(
46
        private HttpRequestHeaders $requestHeaders,
47
    ) {
48
    }
49
50
    /**
51
     * @inheritDoc
52
     * @psalm-taint-sink ssrf $uri
53
     */
54
    #[Override]
55
    public function request(string $method, string $uri, array $query): array
56
    {
57
        $body = http_build_query($query);
58
        $curl = $this->initializeCurl($method, $uri, $body);
59
        $response = (string) curl_exec($curl);
60
        $code = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
61
        $headerSize = (int) curl_getinfo($curl, CURLINFO_HEADER_SIZE);
62
        $headerString = substr($response, 0, $headerSize);
63
        $view = substr($response, $headerSize);
64
        $headers = $this->parseResponseHeaders($headerString);
65
66
        $body = $this->parseBody($curl, $view);
67
68
        return [
69
            'code' => $code,
70
            'headers' => $headers,
71
            'body' => $body,
72
            'view' => $view,
73
        ];
74
    }
75
76
    /** @psalm-taint-sink ssrf $uri */
77
    private function initializeCurl(string $method, string $uri, string $body): CurlHandle
78
    {
79
        $curl = curl_init();
80
        if ($curl === false) {
81
            throw new RuntimeException('Failed to initialize cURL'); // @codeCoverageIgnore
82
        }
83
84
        assert($method !== '');
85
        assert($uri !== '');
86
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
87
        curl_setopt($curl, CURLOPT_URL, $uri);
88
89
        if ($this->requestHeaders->headers !== []) {
90
            curl_setopt($curl, CURLOPT_HTTPHEADER, $this->requestHeaders->headers);
91
        }
92
93
        if ($body !== '') {
94
            // Set the request body
95
            curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
96
        }
97
98
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
99
        curl_setopt($curl, CURLOPT_HEADER, true);
100
101
        return $curl;
102
    }
103
104
    /** @return HttpHeaders */
105
    private function parseResponseHeaders(string $responseHeaders): array
106
    {
107
        $responseHeadersArray = [];
108
        $headerLines = explode("\r\n", $responseHeaders);
109
        foreach ($headerLines as $line) {
110
            $parts = explode(':', $line, 2);
111
            if (count($parts) !== 2) {
112
                continue;
113
            }
114
115
            $key = $parts[0];
116
117
            $responseHeadersArray[$key] = trim($parts[1]);
118
        }
119
120
        return $responseHeadersArray;
121
    }
122
123
    /**
124
     * @return HttpBody
125
     *
126
     * @psalm-taint-source input
127
     */
128
    private function parseBody(CurlHandle $curl, string $view): array
129
    {
130
        $responseBody = [];
131
        $contentType = (string) curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
132
        if (str_contains(strtolower($contentType), 'application/json')) {
133
            return (array) json_decode($view, true);
134
        }
135
136
        return $responseBody;
137
    }
138
}
139