• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sineflow / clamav / 22732759683

05 Mar 2026 05:56PM UTC coverage: 96.0% (+0.5%) from 95.455%
22732759683

push

github

pmishev
Added Scanner::scanStream() method

43 of 46 new or added lines in 4 files covered. (93.48%)

168 of 175 relevant lines covered (96.0%)

107.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.89
/src/Socket/Socket.php
1
<?php
2

3
namespace Sineflow\ClamAV\Socket;
4

5
use Sineflow\ClamAV\Exception\SocketException;
6

7
class Socket
8
{
9
    public const UNIX = \AF_UNIX;
10
    public const NETWORK = \AF_INET;
11

12
    private const MAX_READ_BYTES = 8192;
13

14
    private ?\Socket $socket = null;
15

16
    public function __construct(
340✔
17
        private readonly int $socketType,
18
        private readonly array $connectionArguments,
19
        private readonly ?int $timeoutSeconds = null,
20
    ) {
21
    }
340✔
22

23
    /**
24
     * @throws SocketException
25
     */
26
    public function sendCommand(string $dataIn, int $flagsSend = 0, int $flagsReceive = MSG_WAITALL): string
180✔
27
    {
28
        $this->connect();
180✔
29

30
        $this->sendAll($dataIn, $flagsSend);
180✔
31

32
        return $this->readAndClose($flagsReceive);
180✔
33
    }
34

35
    /**
36
     * Stream data from an already-open resource to ClamAV via INSTREAM protocol.
37
     * The caller owns the stream lifecycle (open/close).
38
     *
39
     * @param resource $stream Open readable stream
40
     *
41
     * @throws SocketException
42
     */
43
    public function sendInstreamFromStream($stream, int $flagsSend = 0, int $flagsReceive = MSG_WAITALL): string
90✔
44
    {
45
        $this->connect();
90✔
46

47
        // Send zINSTREAM\0 command
48
        $command = "zINSTREAM\0";
90✔
49
        $this->sendAll($command, $flagsSend);
90✔
50

51
        while (!feof($stream)) {
90✔
52
            $chunk = fread($stream, self::MAX_READ_BYTES);
90✔
53
            if ($chunk === false) {
90✔
NEW
54
                $this->closeAndThrow('Reading from stream failed');
×
55
            }
56
            if ($chunk === '') {
90✔
NEW
57
                break;
×
58
            }
59
            $chunkLen = strlen($chunk);
90✔
60
            $header = pack('N', $chunkLen);
90✔
61
            $data = $header . $chunk;
90✔
62
            $this->sendAll($data, $flagsSend);
90✔
63
        }
64

65
        // Send terminator (4 zero bytes)
66
        $terminator = pack('N', 0);
90✔
67
        $this->sendAll($terminator, $flagsSend);
90✔
68

69
        return $this->readAndClose($flagsReceive);
90✔
70
    }
71

72
    /**
73
     * @throws SocketException
74
     */
75
    private function readAndClose(int $flagsReceive): string
270✔
76
    {
77
        $dataOut = '';
270✔
78

79
        do {
80
            $bytes = socket_recv($this->socket, $chunk, self::MAX_READ_BYTES, $flagsReceive);
270✔
81
            if (false === $bytes) {
270✔
82
                $this->closeAndThrow('Reading from socket failed');
10✔
83
            }
84
            $dataOut .= $chunk;
260✔
85
        } while ($bytes);
260✔
86
        socket_close($this->socket);
260✔
87
        $this->socket = null;
260✔
88

89
        return rtrim($dataOut, "\0");
260✔
90
    }
91

92
    /**
93
     * @throws SocketException
94
     */
95
    private function connect(): void
270✔
96
    {
97
        if ($this->socket instanceof \Socket) {
270✔
98
            return;
20✔
99
        }
100

101
        $socket = @ socket_create($this->socketType, SOCK_STREAM, 0);
250✔
102
        if ($socket === false) {
250✔
103
            $this->closeAndThrow('Creating socket failed');
×
104
        }
105
        $this->socket = $socket;
250✔
106

107
        $success = @ socket_connect($this->socket, ...$this->connectionArguments);
250✔
108
        if ($success === false) {
250✔
109
            $this->closeAndThrow('Connecting to socket failed');
×
110
        }
111

112
        if ($this->timeoutSeconds !== null) {
250✔
113
            $timeout = ['sec' => $this->timeoutSeconds, 'usec' => 0];
10✔
114
            if (false === socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, $timeout)) {
10✔
115
                $this->closeAndThrow('Setting socket send timeout failed');
×
116
            }
117
            if (false === socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, $timeout)) {
10✔
118
                $this->closeAndThrow('Setting socket receive timeout failed');
×
119
            }
120
        }
121
    }
122

123
    /**
124
     * Loop socket_send() until all bytes are written or an error occurs.
125
     *
126
     * @throws SocketException
127
     */
128
    private function sendAll(string $data, int $flags): void
270✔
129
    {
130
        $length = strlen($data);
270✔
131
        $offset = 0;
270✔
132

133
        while ($offset < $length) {
270✔
134
            $sent = socket_send($this->socket, substr($data, $offset), $length - $offset, $flags);
270✔
135
            if (false === $sent || $sent === 0) {
270✔
NEW
136
                $this->closeAndThrow('Writing to socket failed');
×
137
            }
138
            $offset += $sent;
270✔
139
        }
140
    }
141

142
    private function closeAndThrow(string $message): never
10✔
143
    {
144
        $errorCode = socket_last_error($this->socket);
10✔
145
        if (null !== $this->socket) {
10✔
146
            socket_close($this->socket);
10✔
147
            $this->socket = null;
10✔
148
        }
149
        throw new SocketException($message, $errorCode);
10✔
150
    }
151
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc