extractResponseHeadersAndBody()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
1
<?php
2
/**
3
 * sources.
4
 * Date: 14/08/15
5
 */
6
7
namespace Mailxpert\HttpClients;
8
9
use Mailxpert\Exceptions\MailxpertSDKException;
10
use Mailxpert\Http\RawResponse;
11
12
/**
13
 * Class MailxpertCurlHttpClient
14
 * @package Mailxpert\HttpClients
15
 */
16
class MailxpertCurlHttpClient implements MailxpertHttpClientInterface
17
{
18
    /**
19
     * @var string The client error message
20
     */
21
    protected $curlErrorMessage = '';
22
23
    /**
24
     * @var int The curl client error code
25
     */
26
    protected $curlErrorCode = 0;
27
28
    /**
29
     * @var string|boolean The raw response from the server
30
     */
31
    protected $rawResponse;
32
33
    /**
34
     * @var MailxpertCurl Procedural curl as object
35
     */
36
    protected $mailxpertCurl;
37
38
    /**
39
     * @const Curl Version which is unaffected by the proxy header length error.
40
     */
41
    const CURL_PROXY_QUIRK_VER = 0x071E00;
42
43
    /**
44
     * @const "Connection Established" header text
45
     */
46
    const CONNECTION_ESTABLISHED = "HTTP/1.0 200 Connection established\r\n\r\n";
47
48
    /**
49
     * @param MailxpertCurl|null $mailxpertCurl Procedural curl as object
50
     */
51
    public function __construct(MailxpertCurl $mailxpertCurl = null)
52
    {
53
        $this->mailxpertCurl = $mailxpertCurl ?: new MailxpertCurl();
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function send($url, $method, $body, array $headers, $timeOut)
60
    {
61
        $this->openConnection($url, $method, $body, $headers, $timeOut);
62
        $this->sendRequest();
63
64
        if ($curlErrorCode = $this->mailxpertCurl->errno()) {
65
            throw new MailxpertSDKException($this->mailxpertCurl->error(), $curlErrorCode);
66
        }
67
68
        // Separate the raw headers from the raw body
69
        list($rawHeaders, $rawBody) = $this->extractResponseHeadersAndBody();
70
71
        $this->closeConnection();
72
73
        return new RawResponse($rawHeaders, $rawBody);
74
    }
75
76
    /**
77
     * Opens a new curl connection.
78
     *
79
     * @param string $url     The endpoint to send the request to.
80
     * @param string $method  The request method.
81
     * @param string $body    The body of the request.
82
     * @param array  $headers The request headers.
83
     * @param int    $timeOut The timeout in seconds for the request.
84
     */
85
    public function openConnection($url, $method, $body, array $headers, $timeOut)
86
    {
87
        $options = [
88
            CURLOPT_CUSTOMREQUEST => $method,
89
            CURLOPT_HTTPHEADER => $this->compileRequestHeaders($headers),
90
            CURLOPT_URL => $url,
91
            CURLOPT_CONNECTTIMEOUT => 10,
92
            CURLOPT_TIMEOUT => $timeOut,
93
            CURLOPT_RETURNTRANSFER => true, // Follow 301 redirects
94
            CURLOPT_HEADER => true, // Enable header processing
95
        ];
96
97
        if ($method !== "GET") {
98
            $options[CURLOPT_POSTFIELDS] = $body;
99
        }
100
101
        $this->mailxpertCurl->init();
102
        $this->mailxpertCurl->setoptArray($options);
103
    }
104
105
    /**
106
     * Closes an existing curl connection
107
     */
108
    public function closeConnection()
109
    {
110
        $this->mailxpertCurl->close();
111
    }
112
113
    /**
114
     * Send the request and get the raw response from curl
115
     */
116
    public function sendRequest()
117
    {
118
        $this->rawResponse = $this->mailxpertCurl->exec();
119
    }
120
121
    /**
122
     * Compiles the request headers into a curl-friendly format.
123
     *
124
     * @param array $headers The request headers.
125
     *
126
     * @return array
127
     */
128
    public function compileRequestHeaders(array $headers)
129
    {
130
        $return = [];
131
132
        foreach ($headers as $key => $value) {
133
            $return[] = $key.': '.$value;
134
        }
135
136
        return $return;
137
    }
138
139
    /**
140
     * Extracts the headers and the body into a two-part array
141
     *
142
     * @return array
143
     */
144
    public function extractResponseHeadersAndBody()
145
    {
146
        $headerSize = $this->getHeaderSize();
147
148
        $rawHeaders = mb_substr($this->rawResponse, 0, $headerSize);
149
        $rawBody = mb_substr($this->rawResponse, $headerSize);
150
151
        return [trim($rawHeaders), trim($rawBody)];
152
    }
153
154
    /**
155
     * Return proper header size
156
     *
157
     * @return integer
158
     */
159
    private function getHeaderSize()
160
    {
161
        $headerSize = $this->mailxpertCurl->getinfo(CURLINFO_HEADER_SIZE);
162
        // This corrects a Curl bug where header size does not account
163
        // for additional Proxy headers.
164
        if ($this->needsCurlProxyFix()) {
165
            // Additional way to calculate the request body size.
166
            if (preg_match('/Content-Length: (\d+)/', $this->rawResponse, $m)) {
167
                $headerSize = mb_strlen($this->rawResponse) - $m[1];
168
            } elseif (stripos($this->rawResponse, self::CONNECTION_ESTABLISHED) !== false) {
169
                $headerSize += mb_strlen(self::CONNECTION_ESTABLISHED);
170
            }
171
        }
172
173
        return $headerSize;
174
    }
175
176
    /**
177
     * Detect versions of Curl which report incorrect header lengths when
178
     * using Proxies.
179
     *
180
     * @return boolean
181
     */
182
    private function needsCurlProxyFix()
183
    {
184
        $ver = $this->mailxpertCurl->version();
185
        $version = $ver['version_number'];
186
187
        return $version < self::CURL_PROXY_QUIRK_VER;
188
    }
189
}
190