1<?php
2
3namespace Sabre\HTTP;
4
5/**
6 * PHP SAPI
7 *
8 * This object is responsible for:
9 * 1. Constructing a Request object based on the current HTTP request sent to
10 *    the PHP process.
11 * 2. Sending the Response object back to the client.
12 *
13 * It could be said that this class provides a mapping between the Request and
14 * Response objects, and php's:
15 *
16 * * $_SERVER
17 * * $_POST
18 * * $_FILES
19 * * php://input
20 * * echo()
21 * * header()
22 * * php://output
23 *
24 * You can choose to either call all these methods statically, but you can also
25 * instantiate this as an object to allow for polymorhpism.
26 *
27 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
28 * @author Evert Pot (http://evertpot.com/)
29 * @license http://sabre.io/license/ Modified BSD License
30 */
31class Sapi {
32
33    /**
34     * This static method will create a new Request object, based on the
35     * current PHP request.
36     *
37     * @return Request
38     */
39    static function getRequest() {
40
41        $r = self::createFromServerArray($_SERVER);
42        $r->setBody(fopen('php://input', 'r'));
43        $r->setPostData($_POST);
44        return $r;
45
46    }
47
48    /**
49     * Sends the HTTP response back to a HTTP client.
50     *
51     * This calls php's header() function and streams the body to php://output.
52     *
53     * @param ResponseInterface $response
54     * @return void
55     */
56    static function sendResponse(ResponseInterface $response) {
57
58        header('HTTP/' . $response->getHttpVersion() . ' ' . $response->getStatus() . ' ' . $response->getStatusText());
59        foreach ($response->getHeaders() as $key => $value) {
60
61            foreach ($value as $k => $v) {
62                if ($k === 0) {
63                    header($key . ': ' . $v);
64                } else {
65                    header($key . ': ' . $v, false);
66                }
67            }
68
69        }
70
71        $body = $response->getBody();
72        if (is_null($body)) return;
73
74        $contentLength = $response->getHeader('Content-Length');
75        if ($contentLength !== null) {
76            $output = fopen('php://output', 'wb');
77            if (is_resource($body) && get_resource_type($body) == 'stream') {
78                if (PHP_INT_SIZE !== 4){
79                    // use the dedicated function on 64 Bit systems
80                    stream_copy_to_stream($body, $output, $contentLength);
81                } else {
82                    // workaround for 32 Bit systems to avoid stream_copy_to_stream
83                    while (!feof($body)) {
84                        fwrite($output, fread($body, 8192));
85                    }
86                }
87            } else {
88                fwrite($output, $body, $contentLength);
89            }
90        } else {
91            file_put_contents('php://output', $body);
92        }
93
94        if (is_resource($body)) {
95            fclose($body);
96        }
97
98    }
99
100    /**
101     * This static method will create a new Request object, based on a PHP
102     * $_SERVER array.
103     *
104     * @param array $serverArray
105     * @return Request
106     */
107    static function createFromServerArray(array $serverArray) {
108
109        $headers = [];
110        $method = null;
111        $url = null;
112        $httpVersion = '1.1';
113
114        $protocol = 'http';
115        $hostName = 'localhost';
116
117        foreach ($serverArray as $key => $value) {
118
119            switch ($key) {
120
121                case 'SERVER_PROTOCOL' :
122                    if ($value === 'HTTP/1.0') {
123                        $httpVersion = '1.0';
124                    }
125                    break;
126                case 'REQUEST_METHOD' :
127                    $method = $value;
128                    break;
129                case 'REQUEST_URI' :
130                    $url = $value;
131                    break;
132
133                // These sometimes show up without a HTTP_ prefix
134                case 'CONTENT_TYPE' :
135                    $headers['Content-Type'] = $value;
136                    break;
137                case 'CONTENT_LENGTH' :
138                    $headers['Content-Length'] = $value;
139                    break;
140
141                // mod_php on apache will put credentials in these variables.
142                // (fast)cgi does not usually do this, however.
143                case 'PHP_AUTH_USER' :
144                    if (isset($serverArray['PHP_AUTH_PW'])) {
145                        $headers['Authorization'] = 'Basic ' . base64_encode($value . ':' . $serverArray['PHP_AUTH_PW']);
146                    }
147                    break;
148
149                // Similarly, mod_php may also screw around with digest auth.
150                case 'PHP_AUTH_DIGEST' :
151                    $headers['Authorization'] = 'Digest ' . $value;
152                    break;
153
154                // Apache may prefix the HTTP_AUTHORIZATION header with
155                // REDIRECT_, if mod_rewrite was used.
156                case 'REDIRECT_HTTP_AUTHORIZATION' :
157                    $headers['Authorization'] = $value;
158                    break;
159
160                case 'HTTP_HOST' :
161                    $hostName = $value;
162                    $headers['Host'] = $value;
163                    break;
164
165                case 'HTTPS' :
166                    if (!empty($value) && $value !== 'off') {
167                        $protocol = 'https';
168                    }
169                    break;
170
171                default :
172                    if (substr($key, 0, 5) === 'HTTP_') {
173                        // It's a HTTP header
174
175                        // Normalizing it to be prettier
176                        $header = strtolower(substr($key, 5));
177
178                        // Transforming dashes into spaces, and uppercasing
179                        // every first letter.
180                        $header = ucwords(str_replace('_', ' ', $header));
181
182                        // Turning spaces into dashes.
183                        $header = str_replace(' ', '-', $header);
184                        $headers[$header] = $value;
185
186                    }
187                    break;
188
189
190            }
191
192        }
193
194        $r = new Request($method, $url, $headers);
195        $r->setHttpVersion($httpVersion);
196        $r->setRawServerData($serverArray);
197        $r->setAbsoluteUrl($protocol . '://' . $hostName . $url);
198        return $r;
199
200    }
201
202}
203