Passed
Push — master ( 3aca5c...c8983c )
by Petr
02:24
created

Answer   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 203
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 84
dl 0
loc 203
ccs 93
cts 93
cp 1
rs 9.76
c 0
b 0
f 0
wmc 33

16 Methods

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