Passed
Push — master ( 2d5568...2edf78 )
by Radu
01:14
created

CurlBrowser::getResponseHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
namespace WebServCo\Framework;
3
4
use WebServCo\Framework\Http;
5
use WebServCo\Framework\Exceptions\ApplicationException;
6
7
final class CurlBrowser implements
8
    \WebServCo\Framework\Interfaces\HttpBrowserInterface
9
{
10
    protected $debug;
11
    protected $skipSslVerification;
12
    protected $requestHeaders;
13
14
    protected $method;
15
    protected $postData;
16
17
    protected $curl;
18
    protected $debugStderr;
19
    protected $debugOutput;
20
    protected $debugInfo;
21
    protected $response;
22
    protected $responseHeaders;
23
24
    protected $logger;
25
26
    protected $curlError;
27
28
    public function __construct(\WebServCo\Framework\Interfaces\LoggerInterface $logger)
29
    {
30
        $this->logger = $logger;
31
        $this->debug = false;
32
        $this->skipSslVerification = false;
33
        $this->requestHeaders = [];
34
    }
35
36
    public function setDebug(bool $debug)
37
    {
38
        $this->debug = $debug;
39
    }
40
41
    public function setSkipSSlVerification(bool $skipSslVerification)
42
    {
43
        $this->skipSslVerification = $skipSslVerification;
44
    }
45
46
    public function setRequestHeader($name, $value)
47
    {
48
        $this->requestHeaders[$name] = $value;
49
    }
50
51
    public function getResponseHeaders()
52
    {
53
        return $this->responseHeaders;
54
    }
55
56
    public function get($url)
57
    {
58
        $this->setMethod(Http::METHOD_GET);
59
        return $this->retrieve($url);
60
    }
61
62
    public function head($url)
63
    {
64
        $this->setMethod(Http::METHOD_HEAD);
65
        return $this->retrieve($url);
66
    }
67
68
    public function post($url, $postData = [])
69
    {
70
        $this->setMethod(Http::METHOD_POST);
71
        $this->setPostData($postData);
72
        return $this->retrieve($url);
73
    }
74
75
    protected function setMethod($method)
76
    {
77
        if (!in_array($method, Http::getMethods())) {
78
            throw new ApplicationException('Unsupported method');
79
        }
80
        $this->method = $method;
81
        return true;
82
    }
83
84
    protected function setPostData(array $postData)
85
    {
86
        $this->postData = $postData;
87
    }
88
89
    protected function debugInit()
90
    {
91
        if ($this->debug) {
92
            ob_start();
93
            $this->debugStderr = fopen('php://output', 'w');
94
            return true;
95
        }
96
        return false;
97
    }
98
99
    protected function debugDo()
100
    {
101
        if ($this->debug) {
102
            //curl_setopt($this->curl, CURLINFO_HEADER_OUT, 1); /* verbose not working if this is enabled */
103
            curl_setopt($this->curl, CURLOPT_VERBOSE, 1);
104
            curl_setopt($this->curl, CURLOPT_STDERR, $this->debugStderr);
105
            return false;
106
        }
107
        return false;
108
    }
109
110
    protected function debugFinish()
111
    {
112
        if ($this->debug) {
113
            fclose($this->debugStderr);
114
            $this->debugOutput = ob_get_clean();
115
116
            $this->logger->debug('CURL INFO:', $this->debugInfo);
117
            $this->logger->debug('CURL VERBOSE:', $this->debugOutput);
118
            $this->logger->debug('CURL RESPONSE:', $this->response);
119
120
            return true;
121
        }
122
        return false;
123
    }
124
125
    protected function getHttpCode()
126
    {
127
        return isset($this->debugInfo['http_code']) ? $this->debugInfo['http_code']: false;
128
    }
129
130
    protected function parseRequestHeaders($headers)
131
    {
132
        $data = [];
133
        foreach ($headers as $k => $v) {
134
            if (is_array($v)) {
135
                foreach ($v as $item) {
136
                    $data[] = sprintf('%s: %s', $k, $item);
137
                }
138
            } else {
139
                $data[] = sprintf('%s: %s', $k, $v);
140
            }
141
        }
142
        return $data;
143
    }
144
145
    protected function parseResponseHeaders($headerString)
146
    {
147
        $headers = [];
148
        $lines = explode("\r\n", $headerString);
149
        foreach ($lines as $index => $line) {
150
            if (0 === $index) {
151
                continue; /* we'll get the status code elsewhere */
152
            }
153
            $parts = explode(': ', $line, 2);
154
            if (!isset($parts[1])) {
155
                continue; // invalid header (missing colon)
156
            }
157
            list($key, $value) = $parts;
158
            if (isset($headers[$key])) {
159
                if (!is_array($headers[$key])) {
160
                    $headers[$key] = [$headers[$key]];
161
                }
162
                // check cookies
163
                if ('Set-Cookie' == $key) {
164
                    $parts = explode('=', $value, 2);
165
                    $cookieName = $parts[0];
166
                    if (is_array($headers[$key])) {
167
                        foreach ($headers[$key] as $cookieIndex => $existingCookie) {
168
                            //check if we already have a cookie with the same name
169
                            if (0 === mb_stripos($existingCookie, $cookieName)) {
170
                                // remove previous cookie with the same name
171
                                unset($headers[$key][$cookieIndex]);
172
                            }
173
                        }
174
                    }
175
                }
176
                $headers[$key][] = $value;
177
                $headers[$key] = array_values((array) $headers[$key]); // re-index array
178
            } else {
179
                $headers[$key] = $value;
180
            }
181
        }
182
        return $headers;
183
    }
184
185
    protected function retrieve($url)
186
    {
187
        $this->debugInit();
188
189
190
        $this->curl = curl_init();
191
        if (!is_resource($this->curl)) {
192
            throw new ApplicationException('Not a valid resource');
193
        }
194
        curl_setopt_array(
195
            $this->curl,
196
            [
197
                CURLOPT_RETURNTRANSFER => true, /* return instead of outputting */
198
                CURLOPT_URL => $url,
199
                CURLOPT_HEADER => true, /* include the header in the output */
200
                CURLOPT_FOLLOWLOCATION => true, /* follow redirects */
201
                CURLOPT_CONNECTTIMEOUT => 60, // The number of seconds to wait while trying to connect.
202
                CURLOPT_TIMEOUT => 60, // The maximum number of seconds to allow cURL functions to execute.
203
            ]
204
        );
205
        if ($this->skipSslVerification) {
206
            // stop cURL from verifying the peer's certificate
207
            curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false);
208
            // don't check the existence of a common name in the SSL peer certificate
209
            curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, 0);
210
        }
211
        if (!empty($this->requestHeaders)) {
212
            curl_setopt(
213
                $this->curl,
214
                CURLOPT_HTTPHEADER,
215
                $this->parseRequestHeaders($this->requestHeaders)
216
            );
217
        }
218
219
        $this->debugDo();
220
221
        switch ($this->method) {
222
            case Http::METHOD_POST:
223
                curl_setopt($this->curl, CURLOPT_POST, true);
224
                if (!empty($this->postData)) {
225
                    curl_setopt($this->curl, CURLOPT_POSTFIELDS, $this->postData);
226
                }
227
                break;
228
            case Http::METHOD_HEAD:
229
                curl_setopt($this->curl, CURLOPT_NOBODY, true);
230
                break;
231
        }
232
233
        $this->response = curl_exec($this->curl);
234
        $this->curlError = curl_error($this->curl);
235
        if (false === $this->response) {
236
            throw new ApplicationException(sprintf("cURL error: %s", $this->curlError));
237
        }
238
239
        $this->debugInfo = curl_getinfo($this->curl);
240
241
        curl_close($this->curl);
242
243
        $httpCode = $this->getHttpCode();
244
245
        if (empty($httpCode)) {
246
            throw new ApplicationException(sprintf("Empty HTTP status code. cURL error: %s", $this->curlError));
247
        }
248
249
        /**
250
         * For redirects, the response will contain evey header/body pair.
251
         * The last header/body will be at the end of the response.
252
         */
253
        $responseParts = explode("\r\n\r\n", (string) $this->response);
254
        $body = trim((string) array_pop($responseParts));
255
256
        $this->responseHeaders = [];
257
        foreach ($responseParts as $item) {
258
            $this->responseHeaders[] = $this->parseResponseHeaders($item);
259
        }
260
261
        $this->debugFinish();
262
263
        return new \WebServCo\Framework\HttpResponse(
264
            $body,
265
            $httpCode,
266
            end($this->responseHeaders)
267
        );
268
    }
269
}
270