Completed
Push — master ( 8f4643...4ef224 )
by Gaetano
07:48 queued 06:06
created

Http::parseResponseHeaders()   F

Complexity

Conditions 47
Paths > 20000

Size

Total Lines 186

Duplication

Lines 23
Ratio 12.37 %

Code Coverage

Tests 63
CRAP Score 106.643

Importance

Changes 0
Metric Value
cc 47
nc 85575
nop 3
dl 23
loc 186
ccs 63
cts 90
cp 0.7
crap 106.643
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpXmlRpc\Helper;
4
5
use PhpXmlRpc\PhpXmlRpc;
6
7
class Http
8
{
9
    /**
10
     * Decode a string that is encoded with "chunked" transfer encoding as defined in rfc2068 par. 19.4.6
11
     * Code shamelessly stolen from nusoap library by Dietrich Ayala.
12
     *
13
     * @param string $buffer the string to be decoded
14
     *
15
     * @return string
16
     */
17
    public static function decodeChunked($buffer)
18
    {
19
        // length := 0
20
        $length = 0;
21
        $new = '';
22
23
        // read chunk-size, chunk-extension (if any) and crlf
24
        // get the position of the linebreak
25
        $chunkEnd = strpos($buffer, "\r\n") + 2;
26
        $temp = substr($buffer, 0, $chunkEnd);
27
        $chunkSize = hexdec(trim($temp));
28
        $chunkStart = $chunkEnd;
29
        while ($chunkSize > 0) {
30
            $chunkEnd = strpos($buffer, "\r\n", $chunkStart + $chunkSize);
31
32
            // just in case we got a broken connection
33
            if ($chunkEnd == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $chunkEnd of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
34
                $chunk = substr($buffer, $chunkStart);
35
                // append chunk-data to entity-body
36
                $new .= $chunk;
37
                $length += strlen($chunk);
38
                break;
39
            }
40
41
            // read chunk-data and crlf
42
            $chunk = substr($buffer, $chunkStart, $chunkEnd - $chunkStart);
43
            // append chunk-data to entity-body
44
            $new .= $chunk;
45
            // length := length + chunk-size
46
            $length += strlen($chunk);
47
            // read chunk-size and crlf
48
            $chunkStart = $chunkEnd + 2;
49
50
            $chunkEnd = strpos($buffer, "\r\n", $chunkStart) + 2;
51
            if ($chunkEnd == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $chunkEnd of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
52
                break; //just in case we got a broken connection
53
            }
54
            $temp = substr($buffer, $chunkStart, $chunkEnd - $chunkStart);
55
            $chunkSize = hexdec(trim($temp));
56
            $chunkStart = $chunkEnd;
57
        }
58
59
        return $new;
60
    }
61
62
    /**
63
     * Parses HTTP an http response headers and separates them from the body.
64
     *
65
     * @param string $data the http response,headers and body. It will be stripped of headers
66
     * @param bool $headersProcessed when true, we assume that response inflating and dechunking has been already carried out
67
     *
68
     * @return array with keys 'headers' and 'cookies'
69
     * @throws \Exception
70
     */
71 528
    public function parseResponseHeaders(&$data, $headersProcessed = false, $debug=0)
72
    {
73 528
        $httpResponse = array('raw_data' => $data, 'headers'=> array(), 'cookies' => array());
74
75
        // Support "web-proxy-tunnelling" connections for https through proxies
76 528
        if (preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data)) {
77
            // Look for CR/LF or simple LF as line separator,
78
            // (even though it is not valid http)
79
            $pos = strpos($data, "\r\n\r\n");
80 View Code Duplication
            if ($pos || is_int($pos)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
81
                $bd = $pos + 4;
82
            } else {
83
                $pos = strpos($data, "\n\n");
84
                if ($pos || is_int($pos)) {
85
                    $bd = $pos + 2;
86
                } else {
87
                    // No separation between response headers and body: fault?
88
                    $bd = 0;
89
                }
90
            }
91
            if ($bd) {
92
                // this filters out all http headers from proxy.
93
                // maybe we could take them into account, too?
94
                $data = substr($data, $bd);
95
            } else {
96
                error_log('XML-RPC: ' . __METHOD__ . ': HTTPS via proxy error, tunnel connection possibly failed');
97
                throw new \Exception(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (HTTPS via proxy error, tunnel connection possibly failed)', PhpXmlRpc::$xmlrpcerr['http_error']);
98
            }
99
        }
100
101
        // Strip HTTP 1.1 100 Continue header if present
102 528
        while (preg_match('/^HTTP\/1\.1 1[0-9]{2} /', $data)) {
103 1
            $pos = strpos($data, 'HTTP', 12);
104
            // server sent a Continue header without any (valid) content following...
105
            // give the client a chance to know it
106 1
            if (!$pos && !is_int($pos)) {
107
                // works fine in php 3, 4 and 5
108
109
                break;
110
            }
111 1
            $data = substr($data, $pos);
112
        }
113
114
        // When using Curl to query servers using Digest Auth, we get back a double set of http headers.
115
        // We strip out the 1st...
116 528
        if ($headersProcessed && preg_match('/^HTTP\/[0-9.]+ 401 /', $data)) {
117 30
            if (preg_match('/(\r?\n){2}HTTP\/[0-9.]+ 200 /', $data)) {
118 30
                $data = preg_replace('/^HTTP\/[0-9.]+ 401 .+?(?:\r?\n){2}(HTTP\/[0-9.]+ 200 )/s', '$1', $data, 1);
119
            }
120
        }
121
122 528
        if (!preg_match('/^HTTP\/[0-9.]+ 200 /', $data)) {
123 1
            $errstr = substr($data, 0, strpos($data, "\n") - 1);
124 1
            error_log('XML-RPC: ' . __METHOD__ . ': HTTP error, got response: ' . $errstr);
125 1
            throw new \Exception(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (' . $errstr . ')', PhpXmlRpc::$xmlrpcerr['http_error']);
126
        }
127
128
        // be tolerant to usage of \n instead of \r\n to separate headers and data
129
        // (even though it is not valid http)
130 527
        $pos = strpos($data, "\r\n\r\n");
131 527 View Code Duplication
        if ($pos || is_int($pos)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
132 526
            $bd = $pos + 4;
133
        } else {
134 1
            $pos = strpos($data, "\n\n");
135 1
            if ($pos || is_int($pos)) {
136 1
                $bd = $pos + 2;
137
            } else {
138
                // No separation between response headers and body: fault?
139
                // we could take some action here instead of going on...
140
                $bd = 0;
141
            }
142
        }
143
144
        // be tolerant to line endings, and extra empty lines
145 527
        $ar = preg_split("/\r?\n/", trim(substr($data, 0, $pos)));
146
147 527
        foreach($ar as $line) {
148
            // take care of multi-line headers and cookies
149 527
            $arr = explode(':', $line, 2);
150 527
            if (count($arr) > 1) {
151 527
                $headerName = strtolower(trim($arr[0]));
152
                /// @todo some other headers (the ones that allow a CSV list of values)
153
                /// do allow many values to be passed using multiple header lines.
154
                /// We should add content to $xmlrpc->_xh['headers'][$headerName]
155
                /// instead of replacing it for those...
156 527
                if ($headerName == 'set-cookie' || $headerName == 'set-cookie2') {
157 18
                    if ($headerName == 'set-cookie2') {
158
                        // version 2 cookies:
159
                        // there could be many cookies on one line, comma separated
160
                        $cookies = explode(',', $arr[1]);
161
                    } else {
162 18
                        $cookies = array($arr[1]);
163
                    }
164 18
                    foreach ($cookies as $cookie) {
165
                        // glue together all received cookies, using a comma to separate them
166
                        // (same as php does with getallheaders())
167 18
                        if (isset($httpResponse['headers'][$headerName])) {
168 18
                            $httpResponse['headers'][$headerName] .= ', ' . trim($cookie);
169
                        } else {
170 18
                            $httpResponse['headers'][$headerName] = trim($cookie);
171
                        }
172
                        // parse cookie attributes, in case user wants to correctly honour them
173
                        // feature creep: only allow rfc-compliant cookie attributes?
174
                        // @todo support for server sending multiple time cookie with same name, but using different PATHs
175 18
                        $cookie = explode(';', $cookie);
176 18
                        foreach ($cookie as $pos => $val) {
177 18
                            $val = explode('=', $val, 2);
178 18
                            $tag = trim($val[0]);
179 18
                            $val = trim(@$val[1]);
180
                            /// @todo with version 1 cookies, we should strip leading and trailing " chars
181 18
                            if ($pos == 0) {
182 18
                                $cookiename = $tag;
183 18
                                $httpResponse['cookies'][$tag] = array();
184 18
                                $httpResponse['cookies'][$cookiename]['value'] = urldecode($val);
185
                            } else {
186 18
                                if ($tag != 'value') {
187 18
                                    $httpResponse['cookies'][$cookiename][$tag] = $val;
0 ignored issues
show
Bug introduced by
The variable $cookiename does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
188
                                }
189
                            }
190
                        }
191
                    }
192
                } else {
193 527
                    $httpResponse['headers'][$headerName] = trim($arr[1]);
194
                }
195
            } elseif (isset($headerName)) {
196
                /// @todo version1 cookies might span multiple lines, thus breaking the parsing above
197 1
                $httpResponse['headers'][$headerName] .= ' ' . trim($line);
198
            }
199
        }
200
201 527
        $data = substr($data, $bd);
202
203 527
        if ($debug && count($httpResponse['headers'])) {
204
            $msg = '';
205
            foreach ($httpResponse['headers'] as $header => $value) {
206
                $msg .= "HEADER: $header: $value\n";
207
            }
208
            foreach ($httpResponse['cookies'] as $header => $value) {
209
                $msg .= "COOKIE: $header={$value['value']}\n";
210
            }
211
            Logger::instance()->debugMessage($msg);
212
        }
213
214
        // if CURL was used for the call, http headers have been processed,
215
        // and dechunking + reinflating have been carried out
216 527
        if (!$headersProcessed) {
217
218
            // Decode chunked encoding sent by http 1.1 servers
219 345
            if (isset($httpResponse['headers']['transfer-encoding']) && $httpResponse['headers']['transfer-encoding'] == 'chunked') {
220
                if (!$data = Http::decodeChunked($data)) {
221
                    error_log('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to rebuild the chunked data received from server');
222
                    throw new \Exception(PhpXmlRpc::$xmlrpcstr['dechunk_fail'], PhpXmlRpc::$xmlrpcerr['dechunk_fail']);
223
                }
224
            }
225
226
            // Decode gzip-compressed stuff
227
            // code shamelessly inspired from nusoap library by Dietrich Ayala
228 345
            if (isset($httpResponse['headers']['content-encoding'])) {
229 60
                $httpResponse['headers']['content-encoding'] = str_replace('x-', '', $httpResponse['headers']['content-encoding']);
230 60
                if ($httpResponse['headers']['content-encoding'] == 'deflate' || $httpResponse['headers']['content-encoding'] == 'gzip') {
231
                    // if decoding works, use it. else assume data wasn't gzencoded
232 60
                    if (function_exists('gzinflate')) {
233 60
                        if ($httpResponse['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) {
234 30
                            $data = $degzdata;
235 30
                            if ($debug) {
236 30
                                Logger::instance()->debugMessage("---INFLATED RESPONSE---[" . strlen($data) . " chars]---\n$data\n---END---");
237
                            }
238 30
                        } elseif ($httpResponse['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) {
239 30
                            $data = $degzdata;
240 30
                            if ($debug) {
241 30
                                Logger::instance()->debugMessage("---INFLATED RESPONSE---[" . strlen($data) . " chars]---\n$data\n---END---");
242
                            }
243
                        } else {
244
                            error_log('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to decode the deflated data received from server');
245 60
                            throw new \Exception(PhpXmlRpc::$xmlrpcstr['decompress_fail'], PhpXmlRpc::$xmlrpcerr['decompress_fail']);
246
                        }
247
                    } else {
248
                        error_log('XML-RPC: ' . __METHOD__ . ': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
249
                        throw new \Exception(PhpXmlRpc::$xmlrpcstr['cannot_decompress'], PhpXmlRpc::$xmlrpcerr['cannot_decompress']);
250
                    }
251
                }
252
            }
253
        } // end of 'if needed, de-chunk, re-inflate response'
254
255 527
        return $httpResponse;
256
    }
257
}
258