Passed
Pull Request — master (#59)
by Raúl
05:19 queued 01:21
created

Response::_interpretHeaders()   B

Complexity

Conditions 8
Paths 48

Size

Total Lines 32
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 15
c 1
b 0
f 0
nc 48
nop 0
dl 0
loc 32
rs 8.4444
1
<?php
2
3
namespace Httpful;
4
5
/**
6
 * Models an HTTP response
7
 *
8
 * @author Nate Good <[email protected]>
9
 */
10
class Response
11
{
12
13
    public $body,
14
           $raw_body,
15
           $headers,
16
           $raw_headers,
17
           $request,
18
           $code = 0,
19
           $content_type,
20
           $parent_type,
21
           $charset,
22
           $meta_data,
23
           $is_mime_vendor_specific = false,
24
           $is_mime_personal = false;
25
26
    private $parsers;
0 ignored issues
show
introduced by
The private property $parsers is not used, and could be removed.
Loading history...
27
28
    /**
29
     * @param string $body
30
     * @param string $headers
31
     * @param Request $request
32
     * @param array $meta_data
33
     */
34
    public function __construct($body, $headers, Request $request, array $meta_data = array())
35
    {
36
        $this->request      = $request;
37
        $this->raw_headers  = $headers;
38
        $this->raw_body     = $body;
39
        $this->meta_data    = $meta_data;
40
41
        $this->code         = $this->_parseCode($headers);
42
        $this->headers      = Response\Headers::fromString($headers);
43
44
        $this->_interpretHeaders();
45
46
        $this->body         = $this->_parse($body);
47
    }
48
49
    /**
50
     * Status Code Definitions
51
     *
52
     * Informational 1xx
53
     * Successful    2xx
54
     * Redirection   3xx
55
     * Client Error  4xx
56
     * Server Error  5xx
57
     *
58
     * http://pretty-rfc.herokuapp.com/RFC2616#status.codes
59
     *
60
     * @return bool Did we receive a 4xx or 5xx?
61
     */
62
    public function hasErrors()
63
    {
64
        return $this->code >= 400;
65
    }
66
67
    /**
68
     * @return bool
69
     */
70
    public function hasBody()
71
    {
72
        return !empty($this->body);
73
    }
74
75
    /**
76
     * Parse the response into a clean data structure
77
     * (most often an associative array) based on the expected
78
     * Mime type.
79
     * @param string Http response body
80
     * @return array|string|object the response parse accordingly
81
     */
82
    public function _parse($body)
83
    {
84
        // If the user decided to forgo the automatic
85
        // smart parsing, short circuit.
86
        if (!$this->request->auto_parse) {
87
            return $body;
88
        }
89
90
        // If provided, use custom parsing callback
91
        if (isset($this->request->parse_callback)) {
92
            return call_user_func($this->request->parse_callback, $body);
93
        }
94
95
        // Decide how to parse the body of the response in the following order
96
        //  1. If provided, use the mime type specifically set as part of the `Request`
97
        //  2. If a MimeHandler is registered for the content type, use it
98
        //  3. If provided, use the "parent type" of the mime type from the response
99
        //  4. Default to the content-type provided in the response
100
        $parse_with = $this->request->expected_type;
101
        if (empty($this->request->expected_type)) {
102
            $parse_with = Httpful::hasParserRegistered($this->content_type)
103
                ? $this->content_type
104
                : $this->parent_type;
105
        }
106
107
       return Httpful::get($parse_with)->parse($body);
108
    }
109
110
    /**
111
     * Parse text headers from response into
112
     * array of key value pairs
113
     * @param string $headers raw headers
114
     * @return array parse headers
115
     */
116
    public function _parseHeaders($headers)
117
    {
118
        $headers = preg_split("/(\r|\n)+/", $headers, -1, \PREG_SPLIT_NO_EMPTY);
119
        $parse_headers = array();
120
        for ($i = 1; $i < count($headers); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
Bug introduced by
It seems like $headers can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
        for ($i = 1; $i < count(/** @scrutinizer ignore-type */ $headers); $i++) {
Loading history...
121
            list($key, $raw_value) = explode(':', $headers[$i], 2);
122
            $key = trim($key);
123
            $value = trim($raw_value);
124
            if (array_key_exists($key, $parse_headers)) {
125
                // See HTTP RFC Sec 4.2 Paragraph 5
126
                // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
127
                // If a header appears more than once, it must also be able to
128
                // be represented as a single header with a comma-separated
129
                // list of values.  We transform accordingly.
130
                $parse_headers[$key] .= ',' . $value;
131
            } else {
132
                $parse_headers[$key] = $value;
133
            }
134
        }
135
        return $parse_headers;
136
    }
137
138
    public function _parseCode($headers)
139
    {
140
        $end = strpos($headers, "\r\n");
141
        if ($end === false) $end = strlen($headers);
142
        $parts = explode(' ', substr($headers, 0, $end));
143
        if (count($parts) < 2 || !is_numeric($parts[1])) {
144
            throw new \Exception("Unable to parse response code from HTTP response due to malformed response");
145
        }
146
        return intval($parts[1]);
147
    }
148
149
    /**
150
     * After we've parse the headers, let's clean things
151
     * up a bit and treat some headers specially
152
     */
153
    public function _interpretHeaders()
154
    {
155
        // Parse the Content-Type and charset
156
        $content_type = isset($this->headers['Content-Type']) ? $this->headers['Content-Type'] : '';
157
        $content_type = explode(';', $content_type);
158
159
        $this->content_type = $content_type[0];
160
        if (count($content_type) == 2 && strpos($content_type[1], '=') !== false) {
161
            list($nill, $this->charset) = explode('=', $content_type[1]);
162
        }
163
164
        // RFC 2616 states "text/*" Content-Types should have a default
165
        // charset of ISO-8859-1. "application/*" and other Content-Types
166
        // are assumed to have UTF-8 unless otherwise specified.
167
        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
168
        // http://www.w3.org/International/O-HTTP-charset.en.php
169
        if (!isset($this->charset)) {
170
            $this->charset = substr($this->content_type, 5) === 'text/' ? 'iso-8859-1' : 'utf-8';
171
        }
172
173
        // Is vendor type? Is personal type?
174
        if (strpos($this->content_type, '/') !== false) {
175
            list($type, $sub_type) = explode('/', $this->content_type);
176
            $this->is_mime_vendor_specific = substr($sub_type, 0, 4) === 'vnd.';
177
            $this->is_mime_personal = substr($sub_type, 0, 4) === 'prs.';
178
        }
179
180
        // Parent type (e.g. xml for application/vnd.github.message+xml)
181
        $this->parent_type = $this->content_type;
182
        if (strpos($this->content_type, '+') !== false) {
183
            list($vendor, $this->parent_type) = explode('+', $this->content_type, 2);
184
            $this->parent_type = Mime::getFullMime($this->parent_type);
185
        }
186
    }
187
188
    /**
189
     * @return string
190
     */
191
    public function __toString()
192
    {
193
        return $this->raw_body;
194
    }
195
}
196