Issues (18)

php-src/Protocols/Http/Answer.php (1 issue)

1
<?php
2
3
namespace kalanis\RemoteRequest\Protocols\Http;
4
5
6
use kalanis\RemoteRequest\Interfaces\IRRTranslations;
7
use kalanis\RemoteRequest\Protocols;
8
use kalanis\RemoteRequest\RequestException;
9
use kalanis\RemoteRequest\Traits\TLang;
10
11
12
/**
13
 * Class Answer
14
 * @package kalanis\RemoteRequest\Protocols\Http
15
 * Process server's answer - parse http
16
 *
17
 * hints:
18
 *  - zip, compress, deflate -> now only on blocks - up to 16MB of content
19
 */
20
class Answer extends Protocols\Dummy\Answer
21
{
22
    use TLang;
23
    use Answer\DecodeStreams\TDecoding;
24
    use Answer\DecodeStrings\TDecoding;
25
26
    /** @var string[][] */
27
    protected array $headers = [];
28
    protected int $code = 0;
29
    protected string $reason = '';
30
    protected int $maxHeaderSize = 17000; // over 16384 - 16K
31
    protected int $maxStringSize = 10000;
32
    /** @var int<1, max> */
33
    protected int $seekSize = 1024; // in how big block we will look for delimiters
34
    protected int $seekPos = 1000; // must be reasonably lower than seekSize - because it's necessary to find delimiters even on edges
35
36 22
    public function __construct(?IRRTranslations $lang = null)
37
    {
38 22
        $this->setRRLang($lang);
39
    }
40
41 17
    protected function clearValues(): void
42
    {
43 17
        $this->headers = [];
44 17
        $this->code = 0;
45 17
        $this->body = null;
46
    }
47
48
    /**
49
     * @param resource|string|null $message
50
     * @throws RequestException
51
     * @return $this
52
     */
53 17
    public function setResponse($message): parent
54
    {
55 17
        $this->clearValues();
56 17
        if (is_resource($message)) {
57 10
            $this->processStreamResponse($message);
58 7
        } elseif (is_string($message)) {
59 7
            $this->processStringResponse($message);
60
        }
61 16
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type kalanis\RemoteRequest\Protocols\Http\Answer which is incompatible with the type-hinted return parent.
Loading history...
62
    }
63
64
    /**
65
     * @param resource $message
66
     * @throws RequestException
67
     */
68 10
    protected function processStreamResponse($message): void
69
    {
70 10
        $headerSize = $position = 0;
71 10
        $onlyHeader = false;
72 10
        rewind($message);
73 10
        while ($data = fread($message, $this->seekSize)) {
74 10
            if (false !== $pos = strpos($data, Protocols\Http::DELIMITER . Protocols\Http::DELIMITER)) {
75 7
                $headerSize = $position + $pos;
76 7
                break;
77
            }
78 3
            $position += $this->seekPos;
79 3
            fseek($message, $position);
80
        }
81 10
        if (0 == $headerSize) {
82 3
            $headerSize = $position;
83 3
            $onlyHeader = true;
84
        }
85 10
        if ($headerSize > $this->maxHeaderSize) {
86 1
            throw new RequestException($this->getRRLang()->rrHttpAnswerHeaderTooLarge($this->maxHeaderSize, $headerSize));
87
        }
88 9
        rewind($message);
89 9
        $this->parseHeader(strval(stream_get_contents($message, $headerSize, 0)));
90 9
        if ($onlyHeader) {
91 2
            return;
92
        }
93 7
        $headerSize += strlen(Protocols\Http::DELIMITER . Protocols\Http::DELIMITER);
94 7
        if ($this->bodySizeMightBeTooLarge()) {
95 1
            $this->processStreamBody($message, $headerSize);
96
        } else {
97 6
            $this->processStringBody(strval(stream_get_contents($message, -1, $headerSize)));
98
        }
99
    }
100
101
    /**
102
     * @param string $data
103
     * @throws RequestException
104
     */
105 7
    protected function processStringResponse(string $data): void
106
    {
107 7
        if (false !== strpos($data, Protocols\Http::DELIMITER . Protocols\Http::DELIMITER)) {
108 6
            list($header, $body) = explode(Protocols\Http::DELIMITER . Protocols\Http::DELIMITER, $data, 2);
109 6
            $this->parseHeader($header);
110 6
            if ($this->bodySizeMightBeTooLarge()) {
111 1
                $this->processStreamBodyFromString($body);
112
            } else {
113 6
                $this->processStringBody($body);
114
            }
115
        } else {
116 1
            $this->parseHeader($data);
117 1
            $this->body = null;
118
        }
119
    }
120
121 16
    protected function parseHeader(string $header): void
122
    {
123 16
        $lines = explode(Protocols\Http::DELIMITER, $header);
124 16
        foreach ($lines as $line) {
125 16
            if (preg_match('/HTTP\/[^\s]+\s([0-9]{3})\s(.+)/ui', $line, $matches)) {
126 16
                $this->code = intval($matches[1]);
127 16
                $this->reason = strval($matches[2]);
128
            } else {
129 11
                if (strlen($line) && (false !== strpos($line, ':'))) {
130 10
                    list($key, $value) = explode(': ', $line);
131 10
                    if (!isset($this->headers[$key])) {
132 10
                        $this->headers[$key] = [];
133
                    }
134 10
                    $this->headers[$key][] = $value;
135
                }
136
            }
137
        }
138
    }
139
140 13
    protected function bodySizeMightBeTooLarge(): bool
141
    {
142 13
        return intval($this->getHeader('Content-Length', '0')) > $this->maxStringSize;
143
    }
144
145
    /**
146
     * @param resource $body
147
     * @param int $headerSize
148
     * @throws RequestException
149
     */
150 1
    protected function processStreamBody($body, int $headerSize): void
151
    {
152 1
        $res = Protocols\Helper::getTempStorage();
153 1
        stream_copy_to_stream($body, $res, -1, $headerSize);
154 1
        rewind($res);
155 1
        $this->body = $this->processStreamDecode($res);
156
    }
157
158
    /**
159
     * @param string $body
160
     * @throws RequestException
161
     */
162 1
    protected function processStreamBodyFromString(string $body): void
163
    {
164 1
        $res = Protocols\Helper::getTempStorage();
165 1
        fwrite($res, $body);
166 1
        rewind($res);
167 1
        $this->body = $this->processStreamDecode($res);
168
    }
169
170
    /**
171
     * @param string $body
172
     * @throws RequestException
173
     */
174 11
    protected function processStringBody(string $body): void
175
    {
176 11
        $res = Protocols\Helper::getMemStorage();
177 11
        fwrite($res, $this->processStringDecode($body));
178 11
        rewind($res);
179 11
        $this->body = $res;
180
    }
181
182 13
    public function getHeader(string $key, ?string $default = null): ?string
183
    {
184 13
        return isset($this->headers[$key])? strval(reset($this->headers[$key])) : $default;
185
    }
186
187
    /**
188
     * @param string $key
189
     * @return string[]
190
     */
191 1
    public function getHeaders(string $key): array
192
    {
193 1
        return isset($this->headers[$key])? $this->headers[$key] : [];
194
    }
195
196
    /**
197
     * Dump all obtained headers - usually for DEVEL
198
     * @return array<string, array<string>>
199
     */
200 5
    public function getAllHeaders(): array
201
    {
202 5
        return $this->headers;
203
    }
204
205 16
    public function getCode(): int
206
    {
207 16
        return intval($this->code);
208
    }
209
210 2
    public function getReason(): string
211
    {
212 2
        return strval($this->reason);
213
    }
214
215 1
    public function isSuccessful(): bool
216
    {
217 1
        return in_array($this->getCode(), [200, 206]);
218
    }
219
}
220