Passed
Push — master ( 169a1f...656146 )
by Radu
01:25
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 = true;
11
    protected $skipSslVerification = false;
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
    public function __construct($logDir, \WebServCo\Framework\Interfaces\RequestInterface $requestInterface)
27
    {
28
        $this->logger = new \WebServCo\Framework\FileLogger(
29
            'CurlBrowser',
30
            $logDir,
31
            $requestInterface
32
        );
33
        $this->logger->clear();
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 setRequestHeaders(array $requestHeaders)
47
    {
48
        $this->requestHeaders = $requestHeaders;
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
            list($key, $value) = explode(': ', $line);
154
            if (isset($headers[$key])) {
155
                if (!is_array($headers[$key])) {
156
                    $headers[$key] = [$headers[$key]];
157
                }
158
                // check cookies
159
                if ('Set-Cookie' == $key) {
160
                    $parts = explode('=', $value, 2);
161
                    $cookieName = $parts[0];
162
                    foreach ($headers[$key] as $index => $existing) {
0 ignored issues
show
Comprehensibility Bug introduced by
$index is overwriting a variable from outer foreach loop.
Loading history...
163
                        //check if we already have a cookie with the same name
164
                        if (0 === mb_stripos($existing, $cookieName)) {
165
                            // remove previous cookie with the same name
166
                            unset($headers[$key][$index]);
167
                        }
168
                    }
169
                }
170
                $headers[$key][] = $value;
171
                $headers[$key] = array_values($headers[$key]); // re-index array
172
            } else {
173
                $headers[$key] = $value;
174
            }
175
        }
176
        return $headers;
177
    }
178
179
    protected function retrieve($url)
180
    {
181
        $this->debugInit();
182
183
184
        $this->curl = curl_init();
185
        curl_setopt_array(
186
            $this->curl,
187
            [
188
                CURLOPT_RETURNTRANSFER => true, /* return instead of outputting */
189
                CURLOPT_URL => $url,
190
                CURLOPT_HEADER => true, /* include the header in the output */
191
                CURLOPT_FOLLOWLOCATION => true, /* follow redirects */
192
            ]
193
        );
194
        if ($this->skipSslVerification) {
195
            // stop cURL from verifying the peer's certificate
196
            curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false);
197
            // don't check the existence of a common name in the SSL peer certificate
198
            curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, 0);
199
        }
200
        if (!empty($this->requestHeaders)) {
201
            curl_setopt(
202
                $this->curl,
203
                CURLOPT_HTTPHEADER,
204
                $this->parseRequestHeaders($this->requestHeaders)
205
            );
206
        }
207
208
        $this->debugDo();
209
210
        switch ($this->method) {
211
            case Http::METHOD_POST:
212
                curl_setopt($this->curl, CURLOPT_POST, true);
213
                if (!empty($this->postData)) {
214
                    curl_setopt($this->curl, CURLOPT_POSTFIELDS, $this->postData);
215
                }
216
                break;
217
            case Http::METHOD_HEAD:
218
                curl_setopt($this->curl, CURLOPT_NOBODY, true);
219
                break;
220
        }
221
222
        $this->response = curl_exec($this->curl);
223
        if (false === $this->response) {
224
            throw new ApplicationException(curl_error($this->curl));
225
        }
226
227
        $this->debugInfo = curl_getinfo($this->curl);
228
229
        $httpCode = $this->getHttpCode();
230
231
        curl_close($this->curl);
232
233
        /**
234
         * For redirects, the response will contain evey header/body pair.
235
         * The last header/body will be at the end of the response.
236
         */
237
        $responseParts = explode("\r\n\r\n", $this->response);
238
        $body = trim(array_pop($responseParts));
239
240
        $this->responseHeaders = [];
241
        foreach ($responseParts as $item) {
242
            $this->responseHeaders[] = $this->parseResponseHeaders($item);
243
        }
244
245
        $this->debugFinish();
246
247
        return new \WebServCo\Framework\HttpResponse(
248
            $body,
249
            $httpCode,
250
            end($this->responseHeaders)
251
        );
252
    }
253
}
254