xref: /PHP-7.3/sapi/fpm/tests/response.inc (revision 1c850bfc)
1<?php
2
3namespace FPM;
4
5class Response
6{
7    const HEADER_SEPARATOR = "\r\n\r\n";
8
9    /**
10     * @var array
11     */
12    private $data;
13
14    /**
15     * @var string
16     */
17    private $rawData;
18
19    /**
20     * @var string
21     */
22    private $rawHeaders;
23
24    /**
25     * @var string
26     */
27    private $rawBody;
28
29    /**
30     * @var array
31     */
32    private $headers;
33
34    /**
35     * @var bool
36     */
37    private $valid;
38
39    /**
40     * @var bool
41     */
42    private $expectInvalid;
43
44    /**
45     * @param string|array|null $data
46     * @param bool $expectInvalid
47     */
48    public function __construct($data = null, $expectInvalid = false)
49    {
50        if (!is_array($data)) {
51            $data = [
52                'response' => $data,
53                'err_response' => null,
54                'out_response' => $data,
55            ];
56        }
57
58        $this->data = $data;
59        $this->expectInvalid = $expectInvalid;
60    }
61
62    /**
63     * @param mixed $body
64     * @param string $contentType
65     * @return Response
66     */
67    public function expectBody($body, $contentType = 'text/html')
68    {
69        if ($multiLine = is_array($body)) {
70            $body = implode("\n", $body);
71        }
72
73        if (
74            $this->checkIfValid() &&
75            $this->checkDefaultHeaders($contentType) &&
76            $body !== $this->rawBody
77        ) {
78            if ($multiLine) {
79                $this->error(
80                    "==> The expected body:\n$body\n" .
81                    "==> does not match the actual body:\n$this->rawBody"
82                );
83            } else {
84                $this->error(
85                    "The expected body '$body' does not match actual body '$this->rawBody'"
86                );
87            }
88        }
89
90        return $this;
91    }
92
93    /**
94     * @return Response
95     */
96    public function expectEmptyBody()
97    {
98        return $this->expectBody('');
99    }
100
101    /**
102     * @param string $contentType
103     * @return string|null
104     */
105    public function getBody($contentType = 'text/html')
106    {
107        if ($this->checkIfValid() && $this->checkDefaultHeaders($contentType)) {
108            return $this->rawBody;
109        }
110
111        return null;
112    }
113
114    /**
115     * Print raw body
116     */
117    public function dumpBody()
118    {
119        var_dump($this->getBody());
120    }
121
122    /**
123     * Print raw body
124     */
125    public function printBody()
126    {
127        echo $this->getBody() . "\n";
128    }
129
130    /**
131     * Debug response output
132     */
133    public function debugOutput()
134    {
135        echo "-------------- RESPONSE: --------------\n";
136        echo "OUT:\n";
137        echo $this->data['out_response'];
138        echo "ERR:\n";
139        echo $this->data['err_response'];
140        echo "---------------------------------------\n\n";
141    }
142
143    /**
144     * @return string|null
145     */
146    public function getErrorData()
147    {
148        return $this->data['err_response'];
149    }
150
151    /**
152     * Check if the response is valid and if not emit error message
153     *
154     * @return bool
155     */
156    private function checkIfValid()
157    {
158        if ($this->isValid()) {
159            return true;
160        }
161
162        if (!$this->expectInvalid) {
163            $this->error("The response is invalid: $this->rawData");
164        }
165
166        return false;
167    }
168
169    /**
170     * @param string $contentType
171     * @return bool
172     */
173    private function checkDefaultHeaders($contentType)
174    {
175        // check default headers
176        return (
177            $this->checkHeader('X-Powered-By', '|^PHP/7|', true) &&
178            $this->checkHeader('Content-type', '|^' . $contentType . '(;\s?charset=\w+)?|', true)
179        );
180    }
181
182    /**
183     * @param string $name
184     * @param string $value
185     * @param bool $useRegex
186     * @return bool
187     */
188    private function checkHeader(string $name, string $value, $useRegex = false)
189    {
190        $lcName = strtolower($name);
191        $headers = $this->getHeaders();
192        if (!isset($headers[$lcName])) {
193            return $this->error("The header $name is not present");
194        }
195        $header = $headers[$lcName];
196
197        if (!$useRegex) {
198            if ($header === $value) {
199                return true;
200            }
201            return $this->error("The header $name value '$header' is not the same as '$value'");
202        }
203
204        if (!preg_match($value, $header)) {
205            return $this->error("The header $name value '$header' does not match RegExp '$value'");
206        }
207
208        return true;
209    }
210
211    /**
212     * @return array|null
213     */
214    private function getHeaders()
215    {
216        if (!$this->isValid()) {
217            return null;
218        }
219
220        if (is_array($this->headers)) {
221            return $this->headers;
222        }
223
224        $headerRows = explode("\r\n", $this->rawHeaders);
225        $headers = [];
226        foreach ($headerRows as $headerRow) {
227            $colonPosition = strpos($headerRow, ':');
228            if ($colonPosition === false) {
229                $this->error("Invalid header row (no colon): $headerRow");
230            }
231            $headers[strtolower(substr($headerRow, 0, $colonPosition))] = trim(
232                substr($headerRow, $colonPosition + 1)
233            );
234        }
235
236        return ($this->headers = $headers);
237    }
238
239    /**
240     * @return bool
241     */
242    private function isValid()
243    {
244        if ($this->valid === null) {
245            $this->processData();
246        }
247
248        return $this->valid;
249    }
250
251    /**
252     * Process data and set validity and raw data
253     */
254    private function processData()
255    {
256        $this->rawData = $this->data['out_response'];
257        $this->valid = (
258            !is_null($this->rawData) &&
259            strpos($this->rawData, self::HEADER_SEPARATOR)
260        );
261        if ($this->valid) {
262            list ($this->rawHeaders, $this->rawBody) = array_map(
263                'trim',
264                explode(self::HEADER_SEPARATOR, $this->rawData)
265            );
266        }
267    }
268
269    /**
270     * Emit error message
271     *
272     * @param string $message
273     * @return bool
274     */
275    private function error($message)
276    {
277        echo "ERROR: $message\n";
278
279        return false;
280    }
281}
282