Passed
Pull Request — master (#36)
by Rogier
01:48
created

Client::parseRawHeaders()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 16
rs 10
cc 3
nc 3
nop 1
1
<?php
2
3
namespace Rogierw\RwAcme\Http;
4
5
use CurlHandle;
6
use Rogierw\RwAcme\Interfaces\HttpClientInterface;
7
8
class Client implements HttpClientInterface
9
{
10
    public function __construct(private readonly int $timeout = 10)
11
    {
12
    }
13
14
    public function head(string $url): Response
15
    {
16
        return $this->makeCurlRequest('head', $url);
17
    }
18
19
    public function get(string $url, array $headers = [], array $arguments = [], int $maxRedirects = 0): Response
20
    {
21
        return $this->makeCurlRequest('get', $url, $headers, $arguments, $maxRedirects);
22
    }
23
24
    public function post(string $url, array $payload = [], array $headers = [], int $maxRedirects = 0): Response
25
    {
26
        $headers = array_merge(['Content-Type: application/jose+json'], $headers);
27
28
        return $this->makeCurlRequest('post', $url, $headers, $payload, $maxRedirects);
29
    }
30
31
    private function makeCurlRequest(string $httpVerb, string $fullUrl, array $headers = [], array $payload = [], int $maxRedirects = 0): Response
32
    {
33
        $headers = array_merge([
34
            'Content-Type: ' . ($httpVerb === 'post') ? 'application/jose+json' : 'application/json',
35
        ], $headers);
36
37
        $curlHandle = $this->getCurlHandle($fullUrl, $headers, $maxRedirects);
38
39
        switch ($httpVerb) {
40
            case 'head':
41
                curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'HEAD');
42
                curl_setopt($curlHandle, CURLOPT_NOBODY, true);
43
                break;
44
45
            case 'get':
46
                curl_setopt($curlHandle, CURLOPT_URL, $fullUrl . '?' . http_build_query($payload));
47
                break;
48
49
            case 'post':
50
                curl_setopt($curlHandle, CURLOPT_POST, true);
51
                $this->attachRequestPayload($curlHandle, $payload);
52
                break;
53
        }
54
55
        $rawResponse = curl_exec($curlHandle);
56
        $headerSize = curl_getinfo($curlHandle, CURLINFO_HEADER_SIZE);
57
        $headers = curl_getinfo($curlHandle);
58
59
        $rawHeaders = mb_substr($rawResponse, 0, $headerSize);
0 ignored issues
show
Bug introduced by
It seems like $rawResponse can also be of type true; however, parameter $string of mb_substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

59
        $rawHeaders = mb_substr(/** @scrutinizer ignore-type */ $rawResponse, 0, $headerSize);
Loading history...
60
        $rawBody = mb_substr($rawResponse, $headerSize);
61
        $body = $rawBody;
62
63
        $allHeaders = array_merge($headers, $this->parseRawHeaders($rawHeaders));
64
65
        if (json_validate($rawBody)) {
66
            $body = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
67
        }
68
69
        return new Response($allHeaders, $allHeaders['url'] ?? '', $allHeaders['http_code'] ?? null, $body);
70
    }
71
72
    private function attachRequestPayload(CurlHandle $curlHandle, array $data): void
73
    {
74
        $encoded = json_encode($data, JSON_THROW_ON_ERROR);
75
76
        curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $encoded);
77
    }
78
79
    private function getCurlHandle(string $fullUrl, array $headers = [], int $maxRedirects = 0): CurlHandle
80
    {
81
        $curlHandle = curl_init();
82
83
        curl_setopt($curlHandle, CURLOPT_URL, $fullUrl);
84
85
        curl_setopt($curlHandle, CURLOPT_HTTPHEADER, array_merge([
86
            'Accept: application/json',
87
        ], $headers));
88
89
        curl_setopt($curlHandle, CURLOPT_USERAGENT, 'rogierw/rw-acme-client');
90
        curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
91
        curl_setopt($curlHandle, CURLOPT_TIMEOUT, $this->timeout);
92
        curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, true);
93
        curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
94
        curl_setopt($curlHandle, CURLOPT_ENCODING, '');
95
        curl_setopt($curlHandle, CURLOPT_HEADER, true);
96
97
        if ($maxRedirects > 0) {
98
            curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, true);
99
            curl_setopt($curlHandle, CURLOPT_MAXREDIRS, $maxRedirects);
100
        }
101
102
        return $curlHandle;
103
    }
104
105
    private function parseRawHeaders(string $rawHeaders): array
106
    {
107
        $headers = explode("\n", $rawHeaders);
108
        $headersArr = [];
109
110
        foreach ($headers as $header) {
111
            if (!str_contains($header, ':')) {
112
                continue;
113
            }
114
115
            [$name, $value] = explode(':', $header, 2);
116
117
            $headersArr[str_replace('_', '-', strtolower($name))] = trim($value);
118
        }
119
120
        return $headersArr;
121
    }
122
}
123