Completed
Push — master ( 5fc0c8...b5d242 )
by Gaetano
06:30
created

Http::parseResponseHeaders()   F

Complexity

Conditions 44
Paths > 20000

Size

Total Lines 174
Code Lines 98

Duplication

Lines 23
Ratio 13.22 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
dl 23
loc 174
rs 2
c 4
b 0
f 0
cc 44
eloc 98
nc 28527
nop 3

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
    public function parseResponseHeaders(&$data, $headersProcessed = false, $debug=0)
72
    {
73
        $httpResponse = array('raw_data' => $data, 'headers'=> array(), 'cookies' => array());
74
75
        // Support "web-proxy-tunelling" connections for https through proxies
76
        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
        while (preg_match('/^HTTP\/1\.1 1[0-9]{2} /', $data)) {
103
            $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
            if (!$pos && !is_int($pos)) {
107
                // works fine in php 3, 4 and 5
108
109
                break;
110
            }
111
            $data = substr($data, $pos);
112
        }
113
        if (!preg_match('/^HTTP\/[0-9.]+ 200 /', $data)) {
114
            $errstr = substr($data, 0, strpos($data, "\n") - 1);
115
            error_log('XML-RPC: ' . __METHOD__ . ': HTTP error, got response: ' . $errstr);
116
            throw new \Exception(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (' . $errstr . ')', PhpXmlRpc::$xmlrpcerr['http_error']);
117
        }
118
119
        // be tolerant to usage of \n instead of \r\n to separate headers and data
120
        // (even though it is not valid http)
121
        $pos = strpos($data, "\r\n\r\n");
122 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...
123
            $bd = $pos + 4;
124
        } else {
125
            $pos = strpos($data, "\n\n");
126
            if ($pos || is_int($pos)) {
127
                $bd = $pos + 2;
128
            } else {
129
                // No separation between response headers and body: fault?
130
                // we could take some action here instead of going on...
131
                $bd = 0;
132
            }
133
        }
134
        // be tolerant to line endings, and extra empty lines
135
        $ar = preg_split("/\r?\n/", trim(substr($data, 0, $pos)));
136
        while (list(, $line) = @each($ar)) {
137
            // take care of multi-line headers and cookies
138
            $arr = explode(':', $line, 2);
139
            if (count($arr) > 1) {
140
                $headerName = strtolower(trim($arr[0]));
141
                /// @todo some other headers (the ones that allow a CSV list of values)
142
                /// do allow many values to be passed using multiple header lines.
143
                /// We should add content to $xmlrpc->_xh['headers'][$headerName]
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
144
                /// instead of replacing it for those...
145
                if ($headerName == 'set-cookie' || $headerName == 'set-cookie2') {
146
                    if ($headerName == 'set-cookie2') {
147
                        // version 2 cookies:
148
                        // there could be many cookies on one line, comma separated
149
                        $cookies = explode(',', $arr[1]);
150
                    } else {
151
                        $cookies = array($arr[1]);
152
                    }
153
                    foreach ($cookies as $cookie) {
154
                        // glue together all received cookies, using a comma to separate them
155
                        // (same as php does with getallheaders())
156
                        if (isset($httpResponse['headers'][$headerName])) {
157
                            $httpResponse['headers'][$headerName] .= ', ' . trim($cookie);
158
                        } else {
159
                            $httpResponse['headers'][$headerName] = trim($cookie);
160
                        }
161
                        // parse cookie attributes, in case user wants to correctly honour them
162
                        // feature creep: only allow rfc-compliant cookie attributes?
163
                        // @todo support for server sending multiple time cookie with same name, but using different PATHs
164
                        $cookie = explode(';', $cookie);
165
                        foreach ($cookie as $pos => $val) {
166
                            $val = explode('=', $val, 2);
167
                            $tag = trim($val[0]);
168
                            $val = trim(@$val[1]);
169
                            /// @todo with version 1 cookies, we should strip leading and trailing " chars
170
                            if ($pos == 0) {
171
                                $cookiename = $tag;
172
                                $httpResponse['cookies'][$tag] = array();
173
                                $httpResponse['cookies'][$cookiename]['value'] = urldecode($val);
174
                            } else {
175
                                if ($tag != 'value') {
176
                                    $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...
177
                                }
178
                            }
179
                        }
180
                    }
181
                } else {
182
                    $httpResponse['headers'][$headerName] = trim($arr[1]);
183
                }
184
            } elseif (isset($headerName)) {
185
                /// @todo version1 cookies might span multiple lines, thus breaking the parsing above
186
                $httpResponse['headers'][$headerName] .= ' ' . trim($line);
187
            }
188
        }
189
190
        $data = substr($data, $bd);
191
192
        if ($debug && count($httpResponse['headers'])) {
193
            $msg = '';
194
            foreach ($httpResponse['headers'] as $header => $value) {
195
                $msg .= "HEADER: $header: $value\n";
196
            }
197
            foreach ($httpResponse['cookies'] as $header => $value) {
198
                $msg .= "COOKIE: $header={$value['value']}\n";
199
            }
200
            Logger::instance()->debugMessage($msg);
201
        }
202
203
        // if CURL was used for the call, http headers have been processed,
204
        // and dechunking + reinflating have been carried out
205
        if (!$headersProcessed) {
206
            // Decode chunked encoding sent by http 1.1 servers
207
            if (isset($httpResponse['headers']['transfer-encoding']) && $httpResponse['headers']['transfer-encoding'] == 'chunked') {
208
                if (!$data = Http::decodeChunked($data)) {
209
                    error_log('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to rebuild the chunked data received from server');
210
                    throw new \Exception(PhpXmlRpc::$xmlrpcstr['dechunk_fail'], PhpXmlRpc::$xmlrpcerr['dechunk_fail']);
211
                }
212
            }
213
214
            // Decode gzip-compressed stuff
215
            // code shamelessly inspired from nusoap library by Dietrich Ayala
216
            if (isset($httpResponse['headers']['content-encoding'])) {
217
                $httpResponse['headers']['content-encoding'] = str_replace('x-', '', $httpResponse['headers']['content-encoding']);
218
                if ($httpResponse['headers']['content-encoding'] == 'deflate' || $httpResponse['headers']['content-encoding'] == 'gzip') {
219
                    // if decoding works, use it. else assume data wasn't gzencoded
220
                    if (function_exists('gzinflate')) {
221
                        if ($httpResponse['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) {
222
                            $data = $degzdata;
223
                            if ($debug) {
224
                                Logger::instance()->debugMessage("---INFLATED RESPONSE---[" . strlen($data) . " chars]---\n$data\n---END---");
225
                            }
226
                        } elseif ($httpResponse['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) {
227
                            $data = $degzdata;
228
                            if ($debug) {
229
                                Logger::instance()->debugMessage("---INFLATED RESPONSE---[" . strlen($data) . " chars]---\n$data\n---END---");
230
                            }
231
                        } else {
232
                            error_log('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to decode the deflated data received from server');
233
                            throw new \Exception(PhpXmlRpc::$xmlrpcstr['decompress_fail'], PhpXmlRpc::$xmlrpcerr['decompress_fail']);
234
                        }
235
                    } else {
236
                        error_log('XML-RPC: ' . __METHOD__ . ': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
237
                        throw new \Exception(PhpXmlRpc::$xmlrpcstr['cannot_decompress'], PhpXmlRpc::$xmlrpcerr['cannot_decompress']);
238
                    }
239
                }
240
            }
241
        } // end of 'if needed, de-chunk, re-inflate response'
242
243
        return $httpResponse;
244
    }
245
}
246