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\AbstractShare; |
11
|
|
|
use Icewind\SMB\ACL; |
12
|
|
|
use Icewind\SMB\Exception\AlreadyExistsException; |
13
|
|
|
use Icewind\SMB\Exception\AuthenticationException; |
14
|
|
|
use Icewind\SMB\Exception\ConnectException; |
15
|
|
|
use Icewind\SMB\Exception\ConnectionException; |
16
|
|
|
use Icewind\SMB\Exception\DependencyException; |
17
|
|
|
use Icewind\SMB\Exception\Exception; |
18
|
|
|
use Icewind\SMB\Exception\FileInUseException; |
19
|
|
|
use Icewind\SMB\Exception\InvalidHostException; |
20
|
|
|
use Icewind\SMB\Exception\InvalidTypeException; |
21
|
|
|
use Icewind\SMB\Exception\NotFoundException; |
22
|
|
|
use Icewind\SMB\Exception\InvalidRequestException; |
23
|
|
|
use Icewind\SMB\IFileInfo; |
24
|
|
|
use Icewind\SMB\INotifyHandler; |
25
|
|
|
use Icewind\SMB\IServer; |
26
|
|
|
use Icewind\SMB\ISystem; |
27
|
|
|
use Icewind\Streams\CallbackWrapper; |
28
|
|
|
use Icewind\SMB\Native\NativeShare; |
29
|
|
|
use Icewind\SMB\Native\NativeServer; |
30
|
|
|
|
31
|
|
|
class Share extends AbstractShare { |
32
|
|
|
/** |
33
|
|
|
* @var IServer $server |
34
|
|
|
*/ |
35
|
|
|
private $server; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var string $name |
39
|
|
|
*/ |
40
|
|
|
private $name; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var Connection|null $connection |
44
|
|
|
*/ |
45
|
|
|
public $connection = null; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var Parser |
49
|
|
|
*/ |
50
|
|
|
protected $parser; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var ISystem |
54
|
|
|
*/ |
55
|
|
|
private $system; |
56
|
|
|
|
57
|
|
|
const MODE_MAP = [ |
58
|
|
|
FileInfo::MODE_READONLY => 'r', |
59
|
|
|
FileInfo::MODE_HIDDEN => 'h', |
60
|
|
|
FileInfo::MODE_ARCHIVE => 'a', |
61
|
|
|
FileInfo::MODE_SYSTEM => 's' |
62
|
|
|
]; |
63
|
|
|
|
64
|
|
|
const EXEC_CMD = 'exec'; |
65
|
|
|
|
66
|
488 |
|
/** |
67
|
488 |
|
* @param IServer $server |
68
|
488 |
|
* @param string $name |
69
|
488 |
|
* @param ISystem $system |
70
|
488 |
|
*/ |
71
|
488 |
|
public function __construct(IServer $server, string $name, ISystem $system) { |
72
|
488 |
|
parent::__construct(); |
73
|
|
|
$this->server = $server; |
74
|
486 |
|
$this->name = $name; |
75
|
486 |
|
$this->system = $system; |
76
|
486 |
|
$this->parser = new Parser($server->getTimeZone()); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
private function getAuthFileArgument(): string { |
80
|
|
|
if ($this->server->getAuth()->getUsername()) { |
81
|
|
|
return '--authentication-file=' . $this->system->getFD(3); |
82
|
486 |
|
} else { |
83
|
486 |
|
return ''; |
84
|
486 |
|
} |
85
|
486 |
|
} |
86
|
486 |
|
|
87
|
486 |
|
protected function getConnection(): Connection { |
88
|
486 |
|
$maxProtocol = $this->server->getOptions()->getMaxProtocol(); |
89
|
486 |
|
$minProtocol = $this->server->getOptions()->getMinProtocol(); |
90
|
486 |
|
$smbClient = $this->system->getSmbclientPath(); |
91
|
486 |
|
$stdBuf = $this->system->getStdBufPath(); |
92
|
|
|
if ($smbClient === null) { |
93
|
486 |
|
throw new Exception("Backend not available"); |
94
|
486 |
|
} |
95
|
486 |
|
$command = sprintf( |
96
|
486 |
|
'%s %s%s -t %s %s %s %s %s %s', |
97
|
|
|
self::EXEC_CMD, |
98
|
|
|
$stdBuf ? $stdBuf . ' -o0 ' : '', |
99
|
|
|
$smbClient, |
100
|
486 |
|
$this->server->getOptions()->getTimeout(), |
101
|
486 |
|
$this->getAuthFileArgument(), |
102
|
|
|
$this->server->getAuth()->getExtraCommandLineArguments(), |
103
|
|
|
$maxProtocol ? "--option='client max protocol=" . $maxProtocol . "'" : "", |
104
|
|
|
$minProtocol ? "--option='client min protocol=" . $minProtocol . "'" : "", |
105
|
|
|
escapeshellarg('//' . $this->server->getHost() . '/' . $this->name) |
106
|
|
|
); |
107
|
|
|
$connection = new Connection($command, $this->parser); |
108
|
|
|
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword()); |
109
|
484 |
|
$connection->connect(); |
110
|
484 |
|
if (!$connection->isValid()) { |
111
|
484 |
|
throw new ConnectionException((string)$connection->readLine()); |
112
|
|
|
} |
113
|
484 |
|
// some versions of smbclient add a help message in first of the first prompt |
114
|
484 |
|
$connection->clearTillPrompt(); |
115
|
|
|
return $connection; |
116
|
2 |
|
} |
117
|
2 |
|
|
118
|
2 |
|
/** |
119
|
|
|
* @throws ConnectionException |
120
|
|
|
* @throws AuthenticationException |
121
|
2 |
|
* @throws InvalidHostException |
122
|
|
|
* @psalm-assert Connection $this->connection |
123
|
|
|
*/ |
124
|
|
|
protected function connect(): Connection { |
125
|
|
|
if ($this->connection and $this->connection->isValid()) { |
126
|
|
|
return $this->connection; |
127
|
|
|
} |
128
|
4 |
|
$this->connection = $this->getConnection(); |
129
|
4 |
|
return $this->connection; |
130
|
|
|
} |
131
|
|
|
|
132
|
484 |
|
/** |
133
|
484 |
|
* @throws ConnectionException |
134
|
484 |
|
* @throws AuthenticationException |
135
|
484 |
|
* @throws InvalidHostException |
136
|
484 |
|
* @psalm-assert Connection $this->connection |
137
|
|
|
*/ |
138
|
|
|
protected function reconnect(): void { |
139
|
|
|
if ($this->connection === null) { |
140
|
|
|
$this->connect(); |
141
|
|
|
} else { |
142
|
|
|
$this->connection->reconnect(); |
143
|
|
|
if (!$this->connection->isValid()) { |
144
|
|
|
throw new ConnectionException(); |
145
|
|
|
} |
146
|
|
|
} |
147
|
|
|
} |
148
|
484 |
|
|
149
|
484 |
|
/** |
150
|
484 |
|
* Get the name of the share |
151
|
|
|
* |
152
|
484 |
|
* @return string |
153
|
484 |
|
*/ |
154
|
|
|
public function getName(): string { |
155
|
484 |
|
return $this->name; |
156
|
|
|
} |
157
|
484 |
|
|
158
|
|
|
protected function simpleCommand(string $command, string $path): bool { |
159
|
484 |
|
$escapedPath = $this->escapePath($path); |
160
|
|
|
$cmd = $command . ' ' . $escapedPath; |
161
|
|
|
$output = $this->execute($cmd); |
162
|
|
|
return $this->parseOutput($output, $path); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
50 |
|
* List the content of a remote folder |
167
|
|
|
* |
168
|
|
|
* @param string $path |
169
|
50 |
|
* @return IFileInfo[] |
170
|
48 |
|
* |
171
|
48 |
|
* @throws NotFoundException |
172
|
48 |
|
* @throws InvalidTypeException |
173
|
46 |
|
*/ |
174
|
48 |
|
public function dir(string $path): array { |
175
|
48 |
|
$escapedPath = $this->escapePath($path); |
176
|
28 |
|
$output = $this->execute('cd ' . $escapedPath); |
177
|
|
|
//check output for errors |
178
|
|
|
$this->parseOutput($output, $path); |
179
|
|
|
$output = $this->execute('dir'); |
180
|
22 |
|
|
181
|
4 |
|
$this->execute('cd /'); |
182
|
|
|
|
183
|
|
|
return $this->parser->parseDir($output, $path, function (string $path) { |
184
|
|
|
return $this->getAcls($path); |
185
|
4 |
|
}); |
186
|
|
|
} |
187
|
|
|
|
188
|
4 |
|
/** |
189
|
2 |
|
* @param string $path |
190
|
|
|
* @return IFileInfo |
191
|
2 |
|
*/ |
192
|
2 |
|
public function stat(string $path): IFileInfo { |
193
|
|
|
// some windows server setups don't seem to like the allinfo command |
194
|
2 |
|
// use the dir command instead to get the file info where possible |
195
|
|
|
if ($path !== "" && $path !== "/") { |
196
|
|
|
$parent = dirname($path); |
197
|
|
|
$dir = $this->dir($parent); |
198
|
|
|
$file = array_values(array_filter($dir, function (IFileInfo $info) use ($path) { |
199
|
|
|
return $info->getPath() === $path; |
200
|
|
|
})); |
201
|
|
|
if ($file) { |
|
|
|
|
202
|
|
|
return $file[0]; |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
|
206
|
484 |
|
$escapedPath = $this->escapePath($path); |
207
|
484 |
|
$output = $this->execute('allinfo ' . $escapedPath); |
208
|
|
|
// Windows and non Windows Fileserver may respond different |
209
|
|
|
// to the allinfo command for directories. If the result is a single |
210
|
|
|
// line = error line, redo it with a different allinfo parameter |
211
|
|
|
if ($escapedPath == '""' && count($output) < 2) { |
212
|
|
|
$output = $this->execute('allinfo ' . '"."'); |
213
|
|
|
} |
214
|
|
|
if (count($output) < 3) { |
215
|
|
|
$this->parseOutput($output, $path); |
216
|
|
|
} |
217
|
|
|
$stat = $this->parser->parseStat($output); |
218
|
|
|
return new FileInfo($path, basename($path), $stat['size'], $stat['mtime'], $stat['mode'], function () use ($path) { |
219
|
482 |
|
return $this->getAcls($path); |
220
|
482 |
|
}); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Create a folder on the share |
225
|
|
|
* |
226
|
|
|
* @param string $path |
227
|
|
|
* @return bool |
228
|
|
|
* |
229
|
|
|
* @throws NotFoundException |
230
|
|
|
* @throws AlreadyExistsException |
231
|
|
|
*/ |
232
|
|
|
public function mkdir(string $path): bool { |
233
|
236 |
|
return $this->simpleCommand('mkdir', $path); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
236 |
|
* Remove a folder on the share |
238
|
22 |
|
* |
239
|
|
|
* @param string $path |
240
|
|
|
* @return bool |
241
|
2 |
|
* |
242
|
2 |
|
* @throws NotFoundException |
243
|
2 |
|
* @throws InvalidTypeException |
244
|
|
|
*/ |
245
|
|
|
public function rmdir(string $path): bool { |
246
|
|
|
return $this->simpleCommand('rmdir', $path); |
247
|
|
|
} |
248
|
20 |
|
|
249
|
2 |
|
/** |
250
|
|
|
* Delete a file on the share |
251
|
|
|
* |
252
|
2 |
|
* @param string $path |
253
|
2 |
|
* @param bool $secondTry |
254
|
|
|
* @return bool |
255
|
|
|
* @throws InvalidTypeException |
256
|
|
|
* @throws NotFoundException |
257
|
|
|
* @throws \Exception |
258
|
|
|
*/ |
259
|
|
|
public function del(string $path, bool $secondTry = false): bool { |
260
|
|
|
//del return a file not found error when trying to delete a folder |
261
|
|
|
//we catch it so we can check if $path doesn't exist or is of invalid type |
262
|
|
|
try { |
263
|
|
|
return $this->simpleCommand('del', $path); |
264
|
|
|
} catch (NotFoundException $e) { |
265
|
|
|
//no need to do anything with the result, we just check if this throws the not found error |
266
|
|
|
try { |
267
|
56 |
|
$this->simpleCommand('ls', $path); |
268
|
56 |
|
} catch (NotFoundException $e2) { |
269
|
38 |
|
throw $e; |
270
|
38 |
|
} catch (\Exception $e2) { |
271
|
38 |
|
throw new InvalidTypeException($path); |
272
|
|
|
} |
273
|
|
|
throw $e; |
274
|
|
|
} catch (FileInUseException $e) { |
275
|
|
|
if ($secondTry) { |
276
|
|
|
throw $e; |
277
|
|
|
} |
278
|
|
|
$this->reconnect(); |
279
|
|
|
return $this->del($path, true); |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
206 |
|
* Rename a remote file |
285
|
206 |
|
* |
286
|
206 |
|
* @param string $from |
287
|
188 |
|
* @param string $to |
288
|
188 |
|
* @return bool |
289
|
|
|
* |
290
|
|
|
* @throws NotFoundException |
291
|
|
|
* @throws AlreadyExistsException |
292
|
|
|
*/ |
293
|
|
View Code Duplication |
public function rename(string $from, string $to): bool { |
|
|
|
|
294
|
|
|
$path1 = $this->escapePath($from); |
295
|
|
|
$path2 = $this->escapePath($to); |
296
|
|
|
$output = $this->execute('rename ' . $path1 . ' ' . $path2); |
297
|
|
|
return $this->parseOutput($output, $to); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
88 |
|
* Upload a local file |
302
|
88 |
|
* |
303
|
70 |
|
* @param string $source local file |
304
|
70 |
|
* @param string $target remove file |
305
|
70 |
|
* @return bool |
306
|
|
|
* |
307
|
|
|
* @throws NotFoundException |
308
|
|
|
* @throws InvalidTypeException |
309
|
|
|
*/ |
310
|
|
View Code Duplication |
public function put(string $source, string $target): bool { |
|
|
|
|
311
|
|
|
$path1 = $this->escapeLocalPath($source); //first path is local, needs different escaping |
312
|
|
|
$path2 = $this->escapePath($target); |
313
|
|
|
$output = $this->execute('put ' . $path1 . ' ' . $path2); |
314
|
|
|
return $this->parseOutput($output, $target); |
315
|
|
|
} |
316
|
|
|
|
317
|
50 |
|
/** |
318
|
50 |
|
* Download a remote file |
319
|
|
|
* |
320
|
|
|
* @param string $source remove file |
321
|
32 |
|
* @param string $target local file |
322
|
|
|
* @return bool |
323
|
32 |
|
* |
324
|
32 |
|
* @throws NotFoundException |
325
|
32 |
|
* @throws InvalidTypeException |
326
|
32 |
|
*/ |
327
|
32 |
View Code Duplication |
public function get(string $source, string $target): bool { |
|
|
|
|
328
|
|
|
$path1 = $this->escapePath($source); |
329
|
|
|
$path2 = $this->escapeLocalPath($target); //second path is local, needs different escaping |
330
|
|
|
$output = $this->execute('get ' . $path1 . ' ' . $path2); |
331
|
|
|
return $this->parseOutput($output, $source); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* Open a readable stream to a remote file |
336
|
|
|
* |
337
|
|
|
* @param string $source |
338
|
|
|
* @return resource a read only stream with the contents of the remote file |
339
|
50 |
|
* |
340
|
50 |
|
* @throws NotFoundException |
341
|
|
|
* @throws InvalidTypeException |
342
|
|
|
*/ |
343
|
32 |
|
public function read(string $source) { |
344
|
|
|
$source = $this->escapePath($source); |
345
|
32 |
|
// since returned stream is closed by the caller we need to create a new instance |
346
|
32 |
|
// since we can't re-use the same file descriptor over multiple calls |
347
|
32 |
|
$connection = $this->getConnection(); |
348
|
|
|
|
349
|
|
|
$connection->write('get ' . $source . ' ' . $this->system->getFD(5)); |
350
|
|
|
$connection->write('exit'); |
351
|
32 |
|
$fh = $connection->getFileOutputStream(); |
352
|
32 |
|
stream_context_set_option($fh, 'file', 'connection', $connection); |
353
|
32 |
|
return $fh; |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* Open a writable stream to a remote file |
358
|
|
|
* |
359
|
|
|
* @param string $target |
360
|
|
|
* @return resource a write only stream to upload a remote file |
361
|
|
|
* |
362
|
|
|
* @throws NotFoundException |
363
|
|
|
* @throws InvalidTypeException |
364
|
2 |
|
*/ |
365
|
2 |
|
public function write(string $target) { |
366
|
|
|
$target = $this->escapePath($target); |
367
|
|
|
// since returned stream is closed by the caller we need to create a new instance |
368
|
|
|
// since we can't re-use the same file descriptor over multiple calls |
369
|
|
|
$connection = $this->getConnection(); |
370
|
|
|
|
371
|
|
|
$fh = $connection->getFileInputStream(); |
372
|
|
|
$connection->write('put ' . $this->system->getFD(4) . ' ' . $target); |
373
|
|
|
$connection->write('exit'); |
374
|
|
|
|
375
|
|
|
// use a close callback to ensure the upload is finished before continuing |
376
|
|
|
// this also serves as a way to keep the connection in scope |
377
|
|
|
$stream = CallbackWrapper::wrap($fh, null, null, function () use ($connection) { |
378
|
|
|
$connection->close(false); // dont terminate, give the upload some time |
379
|
|
|
}); |
380
|
|
|
if (is_resource($stream)) { |
381
|
|
|
return $stream; |
382
|
|
|
} else { |
383
|
|
|
throw new InvalidRequestException($target); |
384
|
|
|
} |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
/** |
388
|
|
|
* Append to stream |
389
|
|
|
* Note: smbclient does not support this (Use php-libsmbclient) |
390
|
|
|
* |
391
|
|
|
* @param string $target |
392
|
|
|
* |
393
|
|
|
* @throws DependencyException |
394
|
|
|
*/ |
395
|
|
|
public function append(string $target) { |
396
|
|
|
throw new DependencyException('php-libsmbclient is required for append'); |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
/** |
400
|
|
|
* @param string $path |
401
|
|
|
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL |
402
|
|
|
* @return mixed |
403
|
2 |
|
*/ |
404
|
2 |
|
public function setMode(string $path, int $mode) { |
405
|
|
|
$modeString = ''; |
406
|
|
|
foreach (self::MODE_MAP as $modeByte => $string) { |
407
|
2 |
|
if ($mode & $modeByte) { |
408
|
2 |
|
$modeString .= $string; |
409
|
2 |
|
} |
410
|
2 |
|
} |
411
|
|
|
$path = $this->escapePath($path); |
412
|
|
|
|
413
|
|
|
// first reset the mode to normal |
414
|
|
|
$cmd = 'setmode ' . $path . ' -rsha'; |
415
|
|
|
$output = $this->execute($cmd); |
416
|
|
|
$this->parseOutput($output, $path); |
417
|
484 |
|
|
418
|
484 |
|
if ($mode !== FileInfo::MODE_NORMAL) { |
419
|
484 |
|
// then set the modes we want |
420
|
484 |
|
$cmd = 'setmode ' . $path . ' ' . $modeString; |
421
|
|
|
$output = $this->execute($cmd); |
422
|
|
|
return $this->parseOutput($output, $path); |
423
|
|
|
} else { |
424
|
|
|
return true; |
425
|
|
|
} |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
/** |
429
|
|
|
* @param string $path |
430
|
|
|
* @return INotifyHandler |
431
|
|
|
* @throws ConnectionException |
432
|
|
|
* @throws DependencyException |
433
|
|
|
*/ |
434
|
|
|
public function notify(string $path): INotifyHandler { |
435
|
|
|
if (!$this->system->getStdBufPath()) { //stdbuf is required to disable smbclient's output buffering |
|
|
|
|
436
|
|
|
throw new DependencyException('stdbuf is required for usage of the notify command'); |
437
|
484 |
|
} |
438
|
484 |
|
$connection = $this->getConnection(); // use a fresh connection since the notify command blocks the process |
439
|
484 |
|
$command = 'notify ' . $this->escapePath($path); |
440
|
|
|
$connection->write($command . PHP_EOL); |
441
|
38 |
|
return new NotifyHandler($connection, $path); |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
/** |
445
|
|
|
* @param string $command |
446
|
|
|
* @return string[] |
447
|
|
|
*/ |
448
|
|
|
protected function execute(string $command): array { |
449
|
|
|
$this->connect()->write($command . PHP_EOL); |
450
|
|
|
return $this->connect()->read(); |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* check output for errors |
455
|
|
|
* |
456
|
|
|
* @param string[] $lines |
457
|
|
|
* @param string $path |
458
|
486 |
|
* |
459
|
486 |
|
* @return bool |
460
|
486 |
|
* @throws AlreadyExistsException |
461
|
2 |
|
* @throws \Icewind\SMB\Exception\AccessDeniedException |
462
|
|
|
* @throws \Icewind\SMB\Exception\NotEmptyException |
463
|
486 |
|
* @throws InvalidTypeException |
464
|
486 |
|
* @throws \Icewind\SMB\Exception\Exception |
465
|
486 |
|
* @throws NotFoundException |
466
|
486 |
|
*/ |
467
|
|
|
protected function parseOutput(array $lines, string $path = ''): bool { |
468
|
|
|
if (count($lines) === 0) { |
469
|
|
|
return true; |
470
|
|
|
} else { |
471
|
|
|
$this->parser->checkForError($lines, $path); |
472
|
|
|
} |
473
|
242 |
|
} |
474
|
242 |
|
|
475
|
242 |
|
/** |
476
|
|
|
* @param string $string |
477
|
|
|
* @return string |
478
|
|
|
*/ |
479
|
|
|
protected function escape(string $string): string { |
480
|
|
|
return escapeshellarg($string); |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
/** |
484
|
|
|
* @param string $path |
485
|
|
|
* @return string |
486
|
|
|
*/ |
487
|
|
|
protected function escapePath(string $path): string { |
488
|
|
|
$this->verifyPath($path); |
489
|
|
|
if ($path === '/') { |
490
|
|
|
$path = ''; |
491
|
|
|
} |
492
|
|
|
$path = str_replace('/', '\\', $path); |
493
|
|
|
$path = str_replace('"', '^"', $path); |
494
|
|
|
$path = ltrim($path, '\\'); |
495
|
|
|
return '"' . $path . '"'; |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
/** |
499
|
|
|
* @param string $path |
500
|
|
|
* @return string |
501
|
|
|
*/ |
502
|
|
|
protected function escapeLocalPath(string $path): string { |
503
|
|
|
$path = str_replace('"', '\"', $path); |
504
|
|
|
return '"' . $path . '"'; |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
/** |
508
|
|
|
* @param string $path |
509
|
|
|
* @return ACL[] |
510
|
|
|
* @throws ConnectionException |
511
|
|
|
* @throws ConnectException |
512
|
|
|
*/ |
513
|
|
|
protected function getAcls(string $path): array { |
514
|
|
|
$commandPath = $this->system->getSmbcAclsPath(); |
515
|
|
|
if (!$commandPath) { |
|
|
|
|
516
|
|
|
return []; |
517
|
|
|
} |
518
|
|
|
|
519
|
|
|
$command = sprintf( |
520
|
|
|
'%s %s %s %s/%s %s', |
521
|
|
|
$commandPath, |
522
|
|
|
$this->getAuthFileArgument(), |
523
|
|
|
$this->server->getAuth()->getExtraCommandLineArguments(), |
524
|
|
|
escapeshellarg('//' . $this->server->getHost()), |
525
|
|
|
escapeshellarg($this->name), |
526
|
|
|
escapeshellarg($path) |
527
|
|
|
); |
528
|
|
|
$connection = new RawConnection($command); |
529
|
|
|
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword()); |
530
|
|
|
$connection->connect(); |
531
|
|
|
if (!$connection->isValid()) { |
532
|
|
|
throw new ConnectionException((string)$connection->readLine()); |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
$rawAcls = $connection->readAll(); |
536
|
|
|
return $this->parser->parseACLs($rawAcls); |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
public function getServer(): IServer { |
540
|
|
|
return $this->server; |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
public function __destruct() { |
544
|
|
|
unset($this->connection); |
545
|
|
|
} |
546
|
|
|
} |
547
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.