|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* This file is part of graze/telnet-client. |
|
5
|
|
|
* |
|
6
|
|
|
* Copyright (c) 2016 Nature Delivered Ltd. <https://www.graze.com> |
|
7
|
|
|
* |
|
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
9
|
|
|
* file that was distributed with this source code. |
|
10
|
|
|
* |
|
11
|
|
|
* @license https://github.com/graze/telnet-client/blob/master/LICENSE |
|
12
|
|
|
* @link https://github.com/graze/telnet-client |
|
13
|
|
|
*/ |
|
14
|
|
|
|
|
15
|
|
|
namespace Graze\TelnetClient; |
|
16
|
|
|
|
|
17
|
|
|
use \Graze\TelnetClient\TelnetClientInterface; |
|
18
|
|
|
use \Graze\TelnetClient\PromptMatcher; |
|
19
|
|
|
use \Graze\TelnetClient\PromptMatcherInterface; |
|
20
|
|
|
use \Graze\TelnetClient\InterpretAsCommand; |
|
21
|
|
|
use \Socket\Raw\Socket; |
|
22
|
|
|
use \Socket\Raw\Factory as SocketFactory; |
|
23
|
|
|
use \Graze\TelnetClient\TelnetClientBuilder; |
|
24
|
|
|
use \Exception; |
|
25
|
|
|
use \Graze\TelnetClient\Exception\TelnetException; |
|
26
|
|
|
|
|
27
|
|
|
class TelnetClient implements TelnetClientInterface |
|
28
|
|
|
{ |
|
29
|
|
|
/** |
|
30
|
|
|
* @var SocketFactory |
|
31
|
|
|
*/ |
|
32
|
|
|
protected $socketFactory; |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* @var PromptMatcherInterface |
|
36
|
|
|
*/ |
|
37
|
|
|
protected $promptMatcher; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* @var InterpretAsCommand |
|
41
|
|
|
*/ |
|
42
|
|
|
protected $interpretAsCommand; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* @var string |
|
46
|
|
|
*/ |
|
47
|
|
|
protected $prompt = '\$'; |
|
48
|
|
|
|
|
49
|
|
|
/** |
|
50
|
|
|
* @var string |
|
51
|
|
|
*/ |
|
52
|
|
|
protected $promptError = 'ERROR'; |
|
53
|
|
|
|
|
54
|
|
|
/** |
|
55
|
|
|
* @var string |
|
56
|
|
|
*/ |
|
57
|
|
|
protected $lineEnding = "\n"; |
|
58
|
|
|
|
|
59
|
|
|
/** |
|
60
|
|
|
* @var Socket |
|
61
|
|
|
*/ |
|
62
|
|
|
protected $socket; |
|
63
|
|
|
|
|
64
|
|
|
/** |
|
65
|
|
|
* @var string |
|
66
|
|
|
*/ |
|
67
|
|
|
protected $buffer; |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* @var string |
|
71
|
|
|
*/ |
|
72
|
|
|
protected $NULL; |
|
73
|
|
|
|
|
74
|
|
|
/** |
|
75
|
|
|
* @var string |
|
76
|
|
|
*/ |
|
77
|
|
|
protected $DC1; |
|
78
|
|
|
|
|
79
|
|
|
/** |
|
80
|
|
|
* @var string |
|
81
|
|
|
*/ |
|
82
|
|
|
protected $IAC; |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* @param SocketFactory $socketFactory |
|
86
|
|
|
* @param PromptMatcherInterface $promptMatcher |
|
87
|
|
|
* @param InterpretAsCommand $interpretAsCommand |
|
88
|
|
|
*/ |
|
89
|
11 |
|
public function __construct( |
|
90
|
|
|
SocketFactory $socketFactory, |
|
91
|
|
|
PromptMatcherInterface $promptMatcher, |
|
92
|
|
|
InterpretAsCommand $interpretAsCommand |
|
93
|
|
|
) { |
|
94
|
11 |
|
$this->socketFactory = $socketFactory; |
|
95
|
11 |
|
$this->promptMatcher = $promptMatcher; |
|
96
|
11 |
|
$this->interpretAsCommand = $interpretAsCommand; |
|
97
|
|
|
|
|
98
|
|
|
$this->NULL = chr(0); |
|
99
|
|
|
$this->DC1 = chr(17); |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
/** |
|
103
|
|
|
* @param string $dsn |
|
104
|
|
|
* @param string $prompt |
|
105
|
|
|
* @param string $promptError |
|
106
|
|
|
* @param string $lineEnding |
|
107
|
|
|
* @param float|null $timeout |
|
108
|
|
|
* |
|
109
|
|
|
* @throws TelnetExceptionInterface |
|
110
|
|
|
*/ |
|
111
|
10 |
|
public function connect($dsn, $prompt = null, $promptError = null, $lineEnding = null, $timeout = null) |
|
112
|
|
|
{ |
|
113
|
10 |
|
if ($prompt !== null) { |
|
114
|
|
|
$this->setPrompt($prompt); |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
10 |
|
if ($promptError !== null) { |
|
118
|
|
|
$this->setPromptError($promptError); |
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
10 |
|
if ($lineEnding !== null) { |
|
122
|
|
|
$this->setLineEnding($lineEnding); |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
|
|
try { |
|
126
|
|
|
$socket = $this->socketFactory->createClient($dsn, $timeout); |
|
|
|
|
|
|
127
|
|
|
} catch (Exception $e) { |
|
128
|
|
|
throw new TelnetException(sprintf('unable to create socket connection to [%s]', $dsn), 0, $e); |
|
129
|
10 |
|
} |
|
130
|
|
|
|
|
131
|
|
|
$this->setSocket($socket); |
|
132
|
3 |
|
} |
|
133
|
|
|
|
|
134
|
|
|
/** |
|
135
|
|
|
* @param string $prompt |
|
136
|
|
|
*/ |
|
137
|
1 |
|
public function setPrompt($prompt) |
|
138
|
|
|
{ |
|
139
|
1 |
|
$this->prompt = $prompt; |
|
140
|
1 |
|
} |
|
141
|
|
|
|
|
142
|
|
|
/** |
|
143
|
|
|
* @param string $promptError |
|
144
|
|
|
*/ |
|
145
|
1 |
|
public function setPromptError($promptError) |
|
146
|
|
|
{ |
|
147
|
1 |
|
$this->promptError = $promptError; |
|
148
|
1 |
|
} |
|
149
|
|
|
|
|
150
|
|
|
/** |
|
151
|
|
|
* @param string $lineEnding |
|
152
|
|
|
*/ |
|
153
|
7 |
|
public function setLineEnding($lineEnding) |
|
154
|
|
|
{ |
|
155
|
7 |
|
$this->lineEnding = $lineEnding; |
|
156
|
7 |
|
} |
|
157
|
|
|
|
|
158
|
|
|
/** |
|
159
|
|
|
* @param Socket $socket |
|
160
|
|
|
*/ |
|
161
|
9 |
|
public function setSocket(Socket $socket) |
|
162
|
|
|
{ |
|
163
|
9 |
|
$this->socket = $socket; |
|
164
|
|
|
} |
|
165
|
|
|
|
|
166
|
|
|
/** |
|
167
|
|
|
* @return \Socket\Raw\Socket |
|
168
|
|
|
*/ |
|
169
|
|
|
public function getSocket() |
|
170
|
|
|
{ |
|
171
|
|
|
return $this->socket; |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
/** |
|
175
|
|
|
* @param string $command |
|
176
|
|
|
* @param string $prompt |
|
177
|
|
|
* |
|
178
|
|
|
* @return \Graze\TelnetClient\TelnetResponseInterface |
|
179
|
|
|
*/ |
|
180
|
10 |
|
public function execute($command, $prompt = null) |
|
181
|
|
|
{ |
|
182
|
10 |
|
if (!$this->socket) { |
|
183
|
|
|
throw new TelnetException('attempt to execute without a connection - call connect first'); |
|
184
|
|
|
} |
|
185
|
|
|
|
|
186
|
1 |
|
$this->write($command); |
|
187
|
1 |
|
return $this->getResponse($prompt); |
|
188
|
3 |
|
} |
|
189
|
|
|
|
|
190
|
|
|
/** |
|
191
|
|
|
* @param string $command |
|
192
|
|
|
* |
|
193
|
|
|
* @return void |
|
194
|
|
|
* @throws TelnetExceptionInterface |
|
195
|
|
|
*/ |
|
196
|
9 |
|
protected function write($command) |
|
197
|
|
|
{ |
|
198
|
|
|
try { |
|
199
|
1 |
|
$this->socket->write($command . $this->lineEnding); |
|
200
|
|
|
} catch (Exception $e) { |
|
201
|
|
|
throw new TelnetException(sprintf('failed writing to socket [%s]', $command), 0, $e); |
|
202
|
9 |
|
} |
|
203
|
8 |
|
} |
|
204
|
|
|
|
|
205
|
|
|
/** |
|
206
|
|
|
* @param string $prompt |
|
207
|
|
|
* |
|
208
|
|
|
* @return \Graze\TelnetClient\TelnetResponseInterface |
|
209
|
|
|
* @throws TelnetExceptionInterface |
|
210
|
|
|
*/ |
|
211
|
8 |
|
protected function getResponse($prompt = null) |
|
212
|
|
|
{ |
|
213
|
8 |
|
$isError = false; |
|
214
|
8 |
|
$buffer = ''; |
|
215
|
|
|
do { |
|
216
|
|
|
// process one character at a time |
|
217
|
|
|
try { |
|
218
|
1 |
|
$character = $this->socket->read(1); |
|
219
|
|
|
} catch (Exception $e) { |
|
220
|
|
|
throw new TelnetException('failed reading from socket', 0, $e); |
|
221
|
1 |
|
} |
|
222
|
|
|
|
|
223
|
|
|
if (in_array($character, [$this->NULL, $this->DC1])) { |
|
224
|
|
|
break; |
|
225
|
|
|
} |
|
226
|
|
|
|
|
227
|
|
|
if ($this->interpretAsCommand->interpret($character, $this->socket)) { |
|
228
|
|
|
continue; |
|
229
|
|
|
} |
|
230
|
|
|
|
|
231
|
|
|
$buffer .= $character; |
|
232
|
|
|
|
|
233
|
|
|
// check for prompt |
|
234
|
|
|
if ($this->promptMatcher->isMatch($prompt ?: $this->prompt, $buffer, $this->lineEnding)) { |
|
235
|
4 |
|
break; |
|
236
|
|
|
} |
|
237
|
|
|
|
|
238
|
|
|
// check for error prompt |
|
239
|
|
|
if ($this->promptMatcher->isMatch($this->promptError, $buffer, $this->lineEnding)) { |
|
240
|
3 |
|
$isError = true; |
|
241
|
3 |
|
break; |
|
242
|
|
|
} |
|
243
|
|
|
} while (true); |
|
244
|
|
|
|
|
245
|
|
|
return new TelnetResponse( |
|
246
|
7 |
|
$isError, |
|
247
|
|
|
$this->promptMatcher->getResponseText(), |
|
248
|
|
|
$this->promptMatcher->getMatches() |
|
249
|
|
|
); |
|
250
|
|
|
} |
|
251
|
|
|
|
|
252
|
|
|
/** |
|
253
|
|
|
* @return TelnetClientInterface |
|
254
|
|
|
*/ |
|
255
|
|
|
public static function factory() |
|
256
|
|
|
{ |
|
257
|
|
|
return new static( |
|
258
|
|
|
new SocketFactory(), |
|
259
|
|
|
new PromptMatcher(), |
|
260
|
|
|
new InterpretAsCommand() |
|
261
|
|
|
); |
|
262
|
|
|
} |
|
263
|
|
|
|
|
264
|
8 |
|
public function __destruct() |
|
265
|
|
|
{ |
|
266
|
8 |
|
if (!$this->socket) { |
|
267
|
1 |
|
return; |
|
268
|
|
|
} |
|
269
|
|
|
|
|
270
|
|
|
$this->socket->close(); |
|
271
|
7 |
|
} |
|
272
|
|
|
} |
|
273
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignorePhpDoc annotation to the duplicate definition and it will be ignored.