1<?php
2/**
3 * Copyright 2007-2016 Horde LLC (http://www.horde.org/)
4 *
5 * @author   Chuck Hagenbuch <chuck@horde.org>
6 * @license  http://www.horde.org/licenses/bsd BSD
7 * @category Horde
8 * @package  Http
9 */
10
11/**
12 * @author   Chuck Hagenbuch <chuck@horde.org>
13 * @license  http://www.horde.org/licenses/bsd BSD
14 * @category Horde
15 * @package  Http
16 */
17abstract class Horde_Http_Response_Base
18{
19    /**
20     * Fetched URI.
21     *
22     * @var string
23     */
24    public $uri;
25
26    /**
27     * HTTP protocol version that was used.
28     *
29     * @var float
30     */
31    public $httpVersion;
32
33    /**
34     * HTTP response code.
35     *
36     * @var integer
37     */
38    public $code;
39
40    /**
41     * Response headers.
42     *
43     * @var array
44     */
45    public $headers;
46
47    /**
48     * Case-insensitive list of headers.
49     *
50     * @var Horde_Support_CaseInsensitiveArray
51     */
52    protected $_headers;
53
54    /**
55     * Parses an array of response headers, mindful of line continuations, etc.
56     *
57     * @param array $headers
58     *
59     * @return array
60     */
61    protected function _parseHeaders($headers)
62    {
63        if (!is_array($headers)) {
64            $headers = preg_split("/\r?\n/", $headers);
65        }
66
67        $this->_headers = new Horde_Support_CaseInsensitiveArray();
68
69        $lastHeader = null;
70        foreach ($headers as $headerLine) {
71            // stream_get_meta returns all headers generated while processing
72            // a request, including ones for redirects before an eventually
73            // successful request. We just want the last one, so whenever we
74            // hit a new HTTP header, throw out anything parsed previously and
75            // start over.
76            if (preg_match('/^HTTP\/(\d.\d) (\d{3})/', $headerLine, $httpMatches)) {
77                $this->httpVersion = $httpMatches[1];
78                $this->code = (int)$httpMatches[2];
79                $this->_headers = new Horde_Support_CaseInsensitiveArray();
80                $lastHeader = null;
81            }
82
83            $headerLine = trim($headerLine, "\r\n");
84            if ($headerLine == '') {
85                break;
86            }
87            if (preg_match('|^([\w-]+):\s+(.+)|', $headerLine, $m)) {
88                $headerName = $m[1];
89                $headerValue = $m[2];
90
91                if ($tmp = $this->_headers[$headerName]) {
92                    if (!is_array($tmp)) {
93                        $tmp = array($tmp);
94                    }
95                    $tmp[] = $headerValue;
96                    $headerValue = $tmp;
97                }
98
99                $this->_headers[$headerName] = $headerValue;
100                $lastHeader = $headerName;
101            } elseif (preg_match("|^\s+(.+)$|", $headerLine, $m) &&
102                      !is_null($lastHeader)) {
103                if (is_array($this->_headers[$lastHeader])) {
104                    $tmp = $this->_headers[$lastHeader];
105                    end($tmp);
106                    $tmp[key($tmp)] .= $m[1];
107                    $this->_headers[$lastHeader] = $tmp;
108                } else {
109                    $this->_headers[$lastHeader] .= $m[1];
110                }
111            }
112        }
113
114        $this->headers = array_change_key_case($this->_headers->getArrayCopy());
115    }
116
117    /**
118     * Returns the body of the HTTP response.
119     *
120     * @throws Horde_Http_Exception
121     * @return string HTTP response body.
122     */
123    abstract public function getBody();
124
125    /**
126     * Returns a stream pointing to the response body that can be used with all
127     * standard PHP stream functions.
128     */
129    public function getStream()
130    {
131        $string_body = $this->getBody();
132        $body = new Horde_Support_StringStream($string_body);
133        return $body->fopen();
134    }
135
136    /**
137     * Returns the value of a single response header.
138     *
139     * @param string $header  Header name to get ('Content-Type',
140     *                        'Content-Length', etc.).
141     *
142     * @return string  HTTP header value.
143     */
144    public function getHeader($header)
145    {
146        return $this->_headers[$header];
147    }
148}
149