Completed
Push — master ( 6bc3b4...34ca55 )
by Ralf
15s queued 11s
created

Response::_interpretHeaders()   B

Complexity

Conditions 8
Paths 48

Size

Total Lines 32
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 15
nc 48
nop 0
dl 0
loc 32
rs 8.4444
c 0
b 0
f 0
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;
27
    /**
28
     * @param string $body
29
     * @param string $headers
30
     * @param Request $request
31
     * @param array $meta_data
32
     */
33
    public function __construct($body, $headers, Request $request, array $meta_data = array())
34
    {
35
        $this->request      = $request;
36
        $this->raw_headers  = $headers;
37
        $this->raw_body     = $body;
38
        $this->meta_data    = $meta_data;
39
40
        $this->code         = $this->_parseCode($headers);
41
        $this->headers      = Response\Headers::fromString($headers);
42
43
        $this->_interpretHeaders();
44
45
        $this->body         = $this->_parse($body);
46
    }
47
48
    /**
49
     * Status Code Definitions
50
     *
51
     * Informational 1xx
52
     * Successful    2xx
53
     * Redirection   3xx
54
     * Client Error  4xx
55
     * Server Error  5xx
56
     *
57
     * http://pretty-rfc.herokuapp.com/RFC2616#status.codes
58
     *
59
     * @return bool Did we receive a 4xx or 5xx?
60
     */
61
    public function hasErrors()
62
    {
63
        return $this->code >= 400;
64
    }
65
66
    /**
67
     * @return return bool
68
     */
69
    public function hasBody()
70
    {
71
        return !empty($this->body);
72
    }
73
74
    /**
75
     * Parse the response into a clean data structure
76
     * (most often an associative array) based on the expected
77
     * Mime type.
78
     * @return array|string|object the response parse accordingly
79
     * @param string Http response body
80
     */
81
    public function _parse($body)
82
    {
83
        // If the user decided to forgo the automatic
84
        // smart parsing, short circuit.
85
        if (!$this->request->auto_parse) {
86
            return $body;
87
        }
88
89
        // If provided, use custom parsing callback
90
        if (isset($this->request->parse_callback)) {
91
            return call_user_func($this->request->parse_callback, $body);
92
        }
93
94
        // Decide how to parse the body of the response in the following order
95
        //  1. If provided, use the mime type specifically set as part of the `Request`
96
        //  2. If a MimeHandler is registered for the content type, use it
97
        //  3. If provided, use the "parent type" of the mime type from the response
98
        //  4. Default to the content-type provided in the response
99
        $parse_with = $this->request->expected_type;
100
        if (empty($this->request->expected_type)) {
101
            $parse_with = Httpful::hasParserRegistered($this->content_type)
102
                ? $this->content_type
103
                : $this->parent_type;
104
        }
105
106
       return Httpful::get($parse_with)->parse($body);
107
    }
108
109
    /**
110
     * Parse text headers from response into
111
     * array of key value pairs
112
     * @return array parse headers
113
     * @param string $headers raw headers
114
     */
115
    public function _parseHeaders($headers)
116
    {
117
        $headers = preg_split("/(\r|\n)+/", $headers, -1, \PREG_SPLIT_NO_EMPTY);
118
        $parse_headers = array();
119
        for ($i = 1; $i < count($headers); $i++) {
120
            list($key, $raw_value) = explode(':', $headers[$i], 2);
121
            $key = trim($key);
122
            $value = trim($raw_value);
123
            if (array_key_exists($key, $parse_headers)) {
124
                // See HTTP RFC Sec 4.2 Paragraph 5
125
                // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
126
                // If a header appears more than once, it must also be able to
127
                // be represented as a single header with a comma-separated
128
                // list of values.  We transform accordingly.
129
                $parse_headers[$key] .= ',' . $value;
130
            } else {
131
                $parse_headers[$key] = $value;
132
            }
133
        }
134
        return $parse_headers;
135
    }
136
137
    public function _parseCode($headers)
138
    {
139
        $end = strpos($headers, "\r\n");
140
        if ($end === false) $end = strlen($headers);
141
        $parts = explode(' ', substr($headers, 0, $end));
142
        if (count($parts) < 2 || !is_numeric($parts[1])) {
143
            throw new \Exception("Unable to parse response code from HTTP response due to malformed response");
144
        }
145
        return intval($parts[1]);
146
    }
147
148
    /**
149
     * After we've parse the headers, let's clean things
150
     * up a bit and treat some headers specially
151
     */
152
    public function _interpretHeaders()
153
    {
154
        // Parse the Content-Type and charset
155
        $content_type = isset($this->headers['Content-Type']) ? $this->headers['Content-Type'] : '';
156
        $content_type = explode(';', $content_type);
157
158
        $this->content_type = $content_type[0];
159
        if (count($content_type) == 2 && strpos($content_type[1], '=') !== false) {
160
            list($nill, $this->charset) = explode('=', $content_type[1]);
161
        }
162
163
        // RFC 2616 states "text/*" Content-Types should have a default
164
        // charset of ISO-8859-1. "application/*" and other Content-Types
165
        // are assumed to have UTF-8 unless otherwise specified.
166
        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
167
        // http://www.w3.org/International/O-HTTP-charset.en.php
168
        if (!isset($this->charset)) {
169
            $this->charset = substr($this->content_type, 5) === 'text/' ? 'iso-8859-1' : 'utf-8';
170
        }
171
172
        // Is vendor type? Is personal type?
173
        if (strpos($this->content_type, '/') !== false) {
174
            list($type, $sub_type) = explode('/', $this->content_type);
175
            $this->is_mime_vendor_specific = substr($sub_type, 0, 4) === 'vnd.';
176
            $this->is_mime_personal = substr($sub_type, 0, 4) === 'prs.';
177
        }
178
179
        // Parent type (e.g. xml for application/vnd.github.message+xml)
180
        $this->parent_type = $this->content_type;
181
        if (strpos($this->content_type, '+') !== false) {
182
            list($vendor, $this->parent_type) = explode('+', $this->content_type, 2);
183
            $this->parent_type = Mime::getFullMime($this->parent_type);
184
        }
185
    }
186
187
    /**
188
     * @return string
189
     */
190
    public function __toString()
191
    {
192
        return $this->raw_body;
193
    }
194
}
195