RawConnection::connect()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.4098

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 9
cts 14
cp 0.6429
rs 9.504
c 0
b 0
f 0
cc 3
nc 3
nop 0
crap 3.4098
1
<?php
2
/**
3
 * Copyright (c) 2014 Robin Appelman <[email protected]>
4
 * This file is licensed under the Licensed under the MIT license:
5
 * http://opensource.org/licenses/MIT
6
 */
7
8
namespace Icewind\SMB\Wrapped;
9
10
use Icewind\SMB\Exception\ConnectException;
11
use Icewind\SMB\Exception\ConnectionException;
12
13
class RawConnection {
14
	/**
15
	 * @var string
16
	 */
17
	private $command;
18
19
	/**
20
	 * @var string[]
21
	 */
22
	private $env;
23
24
	/**
25
	 * @var resource[] $pipes
26
	 *
27
	 * $pipes[0] holds STDIN for smbclient
28
	 * $pipes[1] holds STDOUT for smbclient
29
	 * $pipes[3] holds the authfile for smbclient
30
	 * $pipes[4] holds the stream for writing files
31
	 * $pipes[5] holds the stream for reading files
32
	 */
33
	private $pipes = [];
34
35
	/**
36
	 * @var resource|null $process
37
	 */
38
	private $process;
39
40
	/**
41
	 * @var resource|null $authStream
42
	 */
43
	private $authStream = null;
44
45
	/**
46
	 * @param string $command
47
	 * @param array<string, string> $env
48 494
	 */
49 494
	public function __construct(string $command, array $env = []) {
50 494
		$this->command = $command;
51 494
		$this->env = $env;
52
	}
53
54
	/**
55
	 * @throws ConnectException
56 494
	 * @psalm-assert resource $this->process
57 494
	 */
58
	public function connect(): void {
59
		if (is_null($this->getAuthStream())) {
60
			throw new ConnectException('Authentication not set before connecting');
61
		}
62 494
63
		$descriptorSpec = [
64
			0 => ['pipe', 'r'], // child reads from stdin
65 494
			1 => ['pipe', 'w'], // child writes to stdout
66
			2 => ['pipe', 'w'], // child writes to stderr
67
			3 => $this->getAuthStream(), // child reads from fd#3
68
			4 => ['pipe', 'r'], // child reads from fd#4
69
			5 => ['pipe', 'w']  // child writes to fd#5
70 494
		];
71 494
72 494
		setlocale(LC_ALL, Server::LOCALE);
73
		$env = array_merge($this->env, [
74
			'CLI_FORCE_INTERACTIVE' => 'y', // Needed or the prompt isn't displayed!!
75
			'LC_ALL'                => Server::LOCALE,
76
			'LANG'                  => Server::LOCALE,
77 494
			'COLUMNS'               => 8192 // prevent smbclient from line-wrapping it's output
78 494
		]);
79
		$this->process = proc_open($this->command, $descriptorSpec, $this->pipes, '/', $env);
80
		if (!$this->isValid()) {
81 494
			throw new ConnectionException();
82 494
		}
83
	}
84
85
	/**
86
	 * check if the connection is still active
87
	 *
88
	 * @return bool
89 494
	 * @psalm-assert-if-true resource $this->process
90 494
	 */
91 494
	public function isValid(): bool {
92 494
		if (is_resource($this->process)) {
93
			$status = proc_get_status($this->process);
94
			return (bool)$status['running'];
95
		} else {
96
			return false;
97
		}
98
	}
99
100
	/**
101
	 * send input to the process
102
	 *
103 486
	 * @param string $input
104 486
	 * @return int|bool
105 486
	 */
106 486
	public function write(string $input) {
107
		$result = @fwrite($this->getInputStream(), $input);
108
		fflush($this->getInputStream());
109
		return $result;
110
	}
111
112
	/**
113
	 * read a line of output
114 494
	 *
115 494
	 * @return string|false
116
	 */
117
	public function readLine() {
118
		return stream_get_line($this->getOutputStream(), 4086, "\n");
119
	}
120
121
	/**
122
	 * read a line of output
123
	 *
124
	 * @return string|false
125
	 */
126
	public function readError() {
127
		$line = stream_get_line($this->getErrorStream(), 4086);
128
		return $line !== false ? trim($line) : false;
129
	}
130
131
	/**
132 10
	 * get all output until the process closes
133 10
	 *
134 10
	 * @return string[]
135 10
	 */
136
	public function readAll(): array {
137 10
		$output = [];
138
		while ($line = $this->readLine()) {
139
			$output[] = $line;
140 486
		}
141 486
		return $output;
142
	}
143
144 494
	/**
145 494
	 * @return resource
146
	 */
147
	public function getInputStream() {
148
		return $this->pipes[0];
149
	}
150
151
	/**
152 494
	 * @return resource
153 494
	 */
154
	public function getOutputStream() {
155
		return $this->pipes[1];
156 32
	}
157 32
158
	/**
159
	 * @return resource
160 32
	 */
161 32
	public function getErrorStream() {
162
		return $this->pipes[2];
163
	}
164 494
165 494
	/**
166
	 * @return resource|null
167 494
	 */
168
	public function getAuthStream() {
169 494
		return $this->authStream;
170 494
	}
171 494
172
	/**
173 494
	 * @return resource
174 494
	 */
175 34
	public function getFileInputStream() {
176
		return $this->pipes[4];
177 494
	}
178 494
179
	/**
180 494
	 * @return resource
181 494
	 */
182
	public function getFileOutputStream() {
183 2
		return $this->pipes[5];
184 2
	}
185 2
186 2
	/**
187
	 * @param string|null $user
188 494
	 * @param string|null $password
189 494
	 * @psalm-assert resource $this->authStream
190 494
	 */
191
	public function writeAuthentication(?string $user, ?string $password): void {
192
		$auth = ($password === null)
193
			? "username=$user"
194
			: "username=$user\npassword=$password\n";
195
196
		$this->authStream = fopen('php://temp', 'w+');
197
		fwrite($this->authStream, $auth);
198
	}
199
200
	/**
201
	 * @param bool $terminate
202
	 * @psalm-assert null $this->process
203
	 */
204
	public function close(bool $terminate = true): void {
205
		if (!is_resource($this->process)) {
206
			return;
207
		}
208
		if ($terminate) {
209
			proc_terminate($this->process);
210
		}
211
		proc_close($this->process);
212
		$this->process = null;
213
	}
214
215
	public function reconnect(): void {
216
		$this->close();
217
		$this->connect();
218
	}
219
220
	public function __destruct() {
221
		$this->close();
222
	}
223
}
224