1<?php
2
3namespace Sabre\HTTP;
4
5/**
6 * This is the abstract base class for both the Request and Response objects.
7 *
8 * This object contains a few simple methods that are shared by both.
9 *
10 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
11 * @author Evert Pot (http://evertpot.com/)
12 * @license http://sabre.io/license/ Modified BSD License
13 */
14abstract class Message implements MessageInterface {
15
16    /**
17     * Request body
18     *
19     * This should be a stream resource
20     *
21     * @var resource
22     */
23    protected $body;
24
25    /**
26     * Contains the list of HTTP headers
27     *
28     * @var array
29     */
30    protected $headers = [];
31
32    /**
33     * HTTP message version (1.0 or 1.1)
34     *
35     * @var string
36     */
37    protected $httpVersion = '1.1';
38
39    /**
40     * Returns the body as a readable stream resource.
41     *
42     * Note that the stream may not be rewindable, and therefore may only be
43     * read once.
44     *
45     * @return resource
46     */
47    function getBodyAsStream() {
48
49        $body = $this->getBody();
50        if (is_string($body) || is_null($body)) {
51            $stream = fopen('php://temp', 'r+');
52            fwrite($stream, $body);
53            rewind($stream);
54            return $stream;
55        }
56        return $body;
57
58    }
59
60    /**
61     * Returns the body as a string.
62     *
63     * Note that because the underlying data may be based on a stream, this
64     * method could only work correctly the first time.
65     *
66     * @return string
67     */
68    function getBodyAsString() {
69
70        $body = $this->getBody();
71        if (is_string($body)) {
72            return $body;
73        }
74        if (is_null($body)) {
75            return '';
76        }
77        $contentLength = $this->getHeader('Content-Length');
78        if (is_int($contentLength) || ctype_digit($contentLength)) {
79            return stream_get_contents($body, $contentLength);
80        } else {
81            return stream_get_contents($body);
82        }
83    }
84
85    /**
86     * Returns the message body, as it's internal representation.
87     *
88     * This could be either a string or a stream.
89     *
90     * @return resource|string
91     */
92    function getBody() {
93
94        return $this->body;
95
96    }
97
98    /**
99     * Replaces the body resource with a new stream or string.
100     *
101     * @param resource|string $body
102     */
103    function setBody($body) {
104
105        $this->body = $body;
106
107    }
108
109    /**
110     * Returns all the HTTP headers as an array.
111     *
112     * Every header is returned as an array, with one or more values.
113     *
114     * @return array
115     */
116    function getHeaders() {
117
118        $result = [];
119        foreach ($this->headers as $headerInfo) {
120            $result[$headerInfo[0]] = $headerInfo[1];
121        }
122        return $result;
123
124    }
125
126    /**
127     * Will return true or false, depending on if a HTTP header exists.
128     *
129     * @param string $name
130     * @return bool
131     */
132    function hasHeader($name) {
133
134        return isset($this->headers[strtolower($name)]);
135
136    }
137
138    /**
139     * Returns a specific HTTP header, based on it's name.
140     *
141     * The name must be treated as case-insensitive.
142     * If the header does not exist, this method must return null.
143     *
144     * If a header appeared more than once in a HTTP request, this method will
145     * concatenate all the values with a comma.
146     *
147     * Note that this not make sense for all headers. Some, such as
148     * `Set-Cookie` cannot be logically combined with a comma. In those cases
149     * you *should* use getHeaderAsArray().
150     *
151     * @param string $name
152     * @return string|null
153     */
154    function getHeader($name) {
155
156        $name = strtolower($name);
157
158        if (isset($this->headers[$name])) {
159            return implode(',', $this->headers[$name][1]);
160        }
161        return null;
162
163    }
164
165    /**
166     * Returns a HTTP header as an array.
167     *
168     * For every time the HTTP header appeared in the request or response, an
169     * item will appear in the array.
170     *
171     * If the header did not exists, this method will return an empty array.
172     *
173     * @param string $name
174     * @return string[]
175     */
176    function getHeaderAsArray($name) {
177
178        $name = strtolower($name);
179
180        if (isset($this->headers[$name])) {
181            return $this->headers[$name][1];
182        }
183
184        return [];
185
186    }
187
188    /**
189     * Updates a HTTP header.
190     *
191     * The case-sensitivity of the name value must be retained as-is.
192     *
193     * If the header already existed, it will be overwritten.
194     *
195     * @param string $name
196     * @param string|string[] $value
197     * @return void
198     */
199    function setHeader($name, $value) {
200
201        $this->headers[strtolower($name)] = [$name, (array)$value];
202
203    }
204
205    /**
206     * Sets a new set of HTTP headers.
207     *
208     * The headers array should contain headernames for keys, and their value
209     * should be specified as either a string or an array.
210     *
211     * Any header that already existed will be overwritten.
212     *
213     * @param array $headers
214     * @return void
215     */
216    function setHeaders(array $headers) {
217
218        foreach ($headers as $name => $value) {
219            $this->setHeader($name, $value);
220        }
221
222    }
223
224    /**
225     * Adds a HTTP header.
226     *
227     * This method will not overwrite any existing HTTP header, but instead add
228     * another value. Individual values can be retrieved with
229     * getHeadersAsArray.
230     *
231     * @param string $name
232     * @param string $value
233     * @return void
234     */
235    function addHeader($name, $value) {
236
237        $lName = strtolower($name);
238        if (isset($this->headers[$lName])) {
239            $this->headers[$lName][1] = array_merge(
240                $this->headers[$lName][1],
241                (array)$value
242            );
243        } else {
244            $this->headers[$lName] = [
245                $name,
246                (array)$value
247            ];
248        }
249
250    }
251
252    /**
253     * Adds a new set of HTTP headers.
254     *
255     * Any existing headers will not be overwritten.
256     *
257     * @param array $headers
258     * @return void
259     */
260    function addHeaders(array $headers) {
261
262        foreach ($headers as $name => $value) {
263            $this->addHeader($name, $value);
264        }
265
266    }
267
268
269    /**
270     * Removes a HTTP header.
271     *
272     * The specified header name must be treated as case-insensitive.
273     * This method should return true if the header was successfully deleted,
274     * and false if the header did not exist.
275     *
276     * @param string $name
277     * @return bool
278     */
279    function removeHeader($name) {
280
281        $name = strtolower($name);
282        if (!isset($this->headers[$name])) {
283            return false;
284        }
285        unset($this->headers[$name]);
286        return true;
287
288    }
289
290    /**
291     * Sets the HTTP version.
292     *
293     * Should be 1.0 or 1.1.
294     *
295     * @param string $version
296     * @return void
297     */
298    function setHttpVersion($version) {
299
300        $this->httpVersion = $version;
301
302    }
303
304    /**
305     * Returns the HTTP version.
306     *
307     * @return string
308     */
309    function getHttpVersion() {
310
311        return $this->httpVersion;
312
313    }
314}
315