Completed
Pull Request — master (#57)
by
unknown
04:46
created

Share::rmdir()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 1
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
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;
9
10
use Icewind\SMB\Exception\ConnectionException;
11
use Icewind\SMB\Exception\DependencyException;
12
use Icewind\SMB\Exception\FileInUseException;
13
use Icewind\SMB\Exception\InvalidTypeException;
14
use Icewind\SMB\Exception\NotFoundException;
15
use Icewind\Streams\CallbackWrapper;
16
17
class Share extends AbstractShare {
18
	/**
19
	 * @var Server $server
20
	 */
21
	private $server;
22
23
	/**
24
	 * @var string $name
25
	 */
26
	private $name;
27
28
	/**
29
	 * @var Connection $connection
30
	 */
31
	public $connection;
32
33
	/**
34
	 * @var \Icewind\SMB\Parser
35
	 */
36
	protected $parser;
37
38
	/**
39
	 * @var \Icewind\SMB\System
40
	 */
41
	private $system;
42
43
	/**
44
	 * @param Server $server
45
	 * @param string $name
46
	 * @param System $system
47
	 */
48 512
	public function __construct($server, $name, System $system = null) {
49 512
		parent::__construct();
50 512
		$this->server = $server;
51 512
		$this->name = $name;
52 512
		$this->system = (!is_null($system)) ? $system : new System();
53 512
		$this->parser = new Parser(new TimeZoneProvider($this->server->getHost(), $this->system));
54 512
	}
55
56 510
	protected function getConnection() {
57 510
		$workgroupArgument = ($this->server->getWorkgroup()) ? ' -W ' . escapeshellarg($this->server->getWorkgroup()) : '';
58 510
                $maxProtocolArgument = ($this->server->getMaxProtocol()) ? ' -m ' . escapeshellarg($this->server->getMaxProtocol()) : '';
59 510
		$smbClientPath = $this->system->getSmbclientPath();
60 510
		if (!$smbClientPath) {
61 2
			throw new DependencyException('Can\'t find smbclient binary in path');
62
		}
63 510
		$command = sprintf('%s%s %s %s --authentication-file=%s %s',
64 510
			$this->system->hasStdBuf() ? 'stdbuf -o0 ' : '',
65 510
			$this->system->getSmbclientPath(),
66 510
			$workgroupArgument,
67 510
                        $maxProtocolArgument,
68 510
			System::getFD(3),
69 510
			escapeshellarg('//' . $this->server->getHost() . '/' . $this->name)
70 510
		);
71 510
		$connection = new Connection($command, $this->parser);
72 510
		$connection->writeAuthentication($this->server->getUser(), $this->server->getPassword());
73 510
		if (!$connection->isValid()) {
74
			throw new ConnectionException();
75
		}
76 510
		return $connection;
77
	}
78
79
	/**
80
	 * @throws \Icewind\SMB\Exception\ConnectionException
81
	 * @throws \Icewind\SMB\Exception\AuthenticationException
82
	 * @throws \Icewind\SMB\Exception\InvalidHostException
83
	 */
84 508
	protected function connect() {
85 508
		if ($this->connection and $this->connection->isValid()) {
86 508
			return;
87
		}
88 508
		$this->connection = $this->getConnection();
89 508
	}
90
91 2
	protected function reconnect() {
92 2
		$this->connection->reconnect();
93 2
		$this->connection->writeAuthentication($this->server->getUser(), $this->server->getPassword());
94 2
		if (!$this->connection->isValid()) {
95
			throw new ConnectionException();
96
		}
97 2
	}
98
99
	/**
100
	 * Get the name of the share
101
	 *
102
	 * @return string
103
	 */
104 4
	public function getName() {
105 4
		return $this->name;
106
	}
107
108 508
	protected function simpleCommand($command, $path) {
109 508
		$escapedPath = $this->escapePath($path);
110 508
		$cmd = $command . ' ' . $escapedPath;
111 508
		$output = $this->execute($cmd);
112 508
		return $this->parseOutput($output, $path);
113
	}
114
115
	/**
116
	 * List the content of a remote folder
117
	 *
118
	 * @param $path
119
	 * @return \Icewind\SMB\IFileInfo[]
120
	 *
121
	 * @throws \Icewind\SMB\Exception\NotFoundException
122
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
123
	 */
124 500
	public function dir($path) {
125 500
		$escapedPath = $this->escapePath($path);
126 500
		$output = $this->execute('cd ' . $escapedPath);
127
		//check output for errors
128 500
		$this->parseOutput($output, $path);
129 500
		$output = $this->execute('dir');
130 500
		$this->execute('cd /');
131
132 500
		return $this->parser->parseDir($output, $path);
133
	}
134
135
	/**
136
	 * @param string $path
137
	 * @return \Icewind\SMB\IFileInfo
138
	 */
139 66
	public function stat($path) {
140 66
		$escapedPath = $this->escapePath($path);
141 48
		$output = $this->execute('allinfo ' . $escapedPath);
142
		// Windows and non Windows Fileserver may respond different
143
		// to the allinfo command for directories. If the result is a single
144
		// line = error line, redo it with a different allinfo parameter
145 48
		if ($escapedPath == '""' && count($output) < 2) {
146
			$output = $this->execute('allinfo ' . '"."');
147
		}
148 48
		if (count($output) < 3) {
149 2
			$this->parseOutput($output, $path);
150
		}
151 46
		$stat = $this->parser->parseStat($output);
152 46
		return new FileInfo($path, basename($path), $stat['size'], $stat['mtime'], $stat['mode']);
153
	}
154
155
	/**
156
	 * Create a folder on the share
157
	 *
158
	 * @param string $path
159
	 * @return bool
160
	 *
161
	 * @throws \Icewind\SMB\Exception\NotFoundException
162
	 * @throws \Icewind\SMB\Exception\AlreadyExistsException
163
	 */
164 502
	public function mkdir($path) {
165 502
		return $this->simpleCommand('mkdir', $path);
166
	}
167
168
	/**
169
	 * Remove a folder on the share
170
	 *
171
	 * @param string $path
172
	 * @return bool
173
	 *
174
	 * @throws \Icewind\SMB\Exception\NotFoundException
175
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
176
	 */
177 502
	public function rmdir($path) {
178 502
		return $this->simpleCommand('rmdir', $path);
179
	}
180
181
	/**
182
	 * Delete a file on the share
183
	 *
184
	 * @param string $path
185
	 * @param bool $secondTry
186
	 * @return bool
187
	 * @throws InvalidTypeException
188
	 * @throws NotFoundException
189
	 * @throws \Exception
190
	 */
191 262
	public function del($path, $secondTry = false) {
192
		//del return a file not found error when trying to delete a folder
193
		//we catch it so we can check if $path doesn't exist or is of invalid type
194
		try {
195 262
			return $this->simpleCommand('del', $path);
196 24
		} catch (NotFoundException $e) {
197
			//no need to do anything with the result, we just check if this throws the not found error
198
			try {
199 4
				$this->simpleCommand('ls', $path);
200 4
			} catch (NotFoundException $e2) {
201 2
				throw $e;
202 2
			} catch (\Exception $e2) {
203 2
				throw new InvalidTypeException($path);
204
			}
205
			throw $e;
206 20
		} catch (FileInUseException $e) {
207 2
			if ($secondTry) {
208
				throw $e;
209
			}
210 2
			$this->reconnect();
211 2
			return $this->del($path, true);
212
		}
213
	}
214
215
	/**
216
	 * Rename a remote file
217
	 *
218
	 * @param string $from
219
	 * @param string $to
220
	 * @return bool
221
	 *
222
	 * @throws \Icewind\SMB\Exception\NotFoundException
223
	 * @throws \Icewind\SMB\Exception\AlreadyExistsException
224
	 */
225 58
	public function rename($from, $to) {
226 58
		$path1 = $this->escapePath($from);
227 40
		$path2 = $this->escapePath($to);
228 40
		$output = $this->execute('rename ' . $path1 . ' ' . $path2);
229 40
		return $this->parseOutput($output, $to);
230
	}
231
232
	/**
233
	 * Upload a local file
234
	 *
235
	 * @param string $source local file
236
	 * @param string $target remove file
237
	 * @return bool
238
	 *
239
	 * @throws \Icewind\SMB\Exception\NotFoundException
240
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
241
	 */
242 230
	public function put($source, $target) {
243 230
		$path1 = $this->escapeLocalPath($source); //first path is local, needs different escaping
244 230
		$path2 = $this->escapePath($target);
245 212
		$output = $this->execute('put ' . $path1 . ' ' . $path2);
246 212
		return $this->parseOutput($output, $target);
247
	}
248
249
	/**
250
	 * Download a remote file
251
	 *
252
	 * @param string $source remove file
253
	 * @param string $target local file
254
	 * @return bool
255
	 *
256
	 * @throws \Icewind\SMB\Exception\NotFoundException
257
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
258
	 */
259 88
	public function get($source, $target) {
260 88
		$path1 = $this->escapePath($source);
261 70
		$path2 = $this->escapeLocalPath($target); //second path is local, needs different escaping
262 70
		$output = $this->execute('get ' . $path1 . ' ' . $path2);
263 70
		return $this->parseOutput($output, $source);
264
	}
265
266
	/**
267
	 * Open a readable stream to a remote file
268
	 *
269
	 * @param string $source
270
	 * @return resource a read only stream with the contents of the remote file
271
	 *
272
	 * @throws \Icewind\SMB\Exception\NotFoundException
273
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
274
	 */
275 50
	public function read($source) {
276 50
		$source = $this->escapePath($source);
277
		// since returned stream is closed by the caller we need to create a new instance
278
		// since we can't re-use the same file descriptor over multiple calls
279 32
		$connection = $this->getConnection();
280
281 32
		$connection->write('get ' . $source . ' ' . System::getFD(5));
282 32
		$connection->write('exit');
283 32
		$fh = $connection->getFileOutputStream();
284 32
		stream_context_set_option($fh, 'file', 'connection', $connection);
285 32
		return $fh;
286
	}
287
288
	/**
289
	 * Open a writable stream to a remote file
290
	 *
291
	 * @param string $target
292
	 * @return resource a write only stream to upload a remote file
293
	 *
294
	 * @throws \Icewind\SMB\Exception\NotFoundException
295
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
296
	 */
297 50
	public function write($target) {
298 50
		$target = $this->escapePath($target);
299
		// since returned stream is closed by the caller we need to create a new instance
300
		// since we can't re-use the same file descriptor over multiple calls
301 32
		$connection = $this->getConnection();
302
303 32
		$fh = $connection->getFileInputStream();
304 32
		$connection->write('put ' . System::getFD(4) . ' ' . $target);
305 32
		$connection->write('exit');
306
307
		// use a close callback to ensure the upload is finished before continuing
308
		// this also serves as a way to keep the connection in scope
309 32
		return CallbackWrapper::wrap($fh, null, null, function () use ($connection, $target) {
310 32
			$connection->close(false); // dont terminate, give the upload some time
311 32
		});
312
	}
313
314
	/**
315
	 * @param string $path
316
	 * @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
317
	 * @return mixed
318
	 */
319 16
	public function setMode($path, $mode) {
320 16
		$modeString = '';
321
		$modeMap = array(
322 16
			FileInfo::MODE_READONLY => 'r',
323 16
			FileInfo::MODE_HIDDEN => 'h',
324 16
			FileInfo::MODE_ARCHIVE => 'a',
325 16
			FileInfo::MODE_SYSTEM => 's'
326 16
		);
327 16
		foreach ($modeMap as $modeByte => $string) {
328 16
			if ($mode & $modeByte) {
329 16
				$modeString .= $string;
330 16
			}
331 16
		}
332 16
		$path = $this->escapePath($path);
333
334
		// first reset the mode to normal
335 16
		$cmd = 'setmode ' . $path . ' -rsha';
336 16
		$output = $this->execute($cmd);
337 16
		$this->parseOutput($output, $path);
338
339 16
		if ($mode !== FileInfo::MODE_NORMAL) {
340
			// then set the modes we want
341 16
			$cmd = 'setmode ' . $path . ' ' . $modeString;
342 16
			$output = $this->execute($cmd);
343 16
			return $this->parseOutput($output, $path);
344
		} else {
345 16
			return true;
346
		}
347
	}
348
349
	/**
350
	 * @param string $path
351
	 * @return INotifyHandler
352
	 * @throws ConnectionException
353
	 * @throws DependencyException
354
	 */
355 10
	public function notify($path) {
356 10
		if (!$this->system->hasStdBuf()) { //stdbuf is required to disable smbclient's output buffering
357
			throw new DependencyException('stdbuf is required for usage of the notify command');
358
		}
359 10
		$connection = $this->getConnection(); // use a fresh connection since the notify command blocks the process
360 10
		$command = 'notify ' . $this->escapePath($path);
361 10
		$connection->write($command . PHP_EOL);
362 10
		return new NotifyHandler($connection, $path);
363
	}
364
365
	/**
366
	 * @param string $command
367
	 * @return array
368
	 */
369 508
	protected function execute($command) {
370 508
		$this->connect();
371 508
		$this->connection->write($command . PHP_EOL);
372 508
		$output = $this->connection->read();
373 508
		return $output;
374
	}
375
376
	/**
377
	 * check output for errors
378
	 *
379
	 * @param string[] $lines
380
	 * @param string $path
381
	 *
382
	 * @throws NotFoundException
383
	 * @throws \Icewind\SMB\Exception\AlreadyExistsException
384
	 * @throws \Icewind\SMB\Exception\AccessDeniedException
385
	 * @throws \Icewind\SMB\Exception\NotEmptyException
386
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
387
	 * @throws \Icewind\SMB\Exception\Exception
388
	 * @return bool
389
	 */
390 508
	protected function parseOutput($lines, $path = '') {
391 508
		if (count($lines) === 0) {
392 508
			return true;
393
		} else {
394 38
			$this->parser->checkForError($lines, $path);
395
			return false;
396
		}
397
	}
398
399
	/**
400
	 * @param string $string
401
	 * @return string
402
	 */
403
	protected function escape($string) {
404
		return escapeshellarg($string);
405
	}
406
407
	/**
408
	 * @param string $path
409
	 * @return string
410
	 */
411 510
	protected function escapePath($path) {
412 510
		$this->verifyPath($path);
413 510
		if ($path === '/') {
414 2
			$path = '';
415 2
		}
416 510
		$path = str_replace('/', '\\', $path);
417 510
		$path = str_replace('"', '^"', $path);
418 510
		return '"' . $path . '"';
419
	}
420
421
	/**
422
	 * @param string $path
423
	 * @return string
424
	 */
425 266
	protected function escapeLocalPath($path) {
426 266
		$path = str_replace('"', '\"', $path);
427 266
		return '"' . $path . '"';
428
	}
429
430 512
	public function __destruct() {
431 512
		unset($this->connection);
432 512
	}
433
}
434