Passed
Push — master ( 996151...ffa7be )
by Radu
01:33
created

CurlBrowser::getHttpCode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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