Completed
Push — master ( eda387...29bdeb )
by Robin
04:14
created

Share::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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\Exception\ConnectionException;
12
use Icewind\SMB\Exception\DependencyException;
13
use Icewind\SMB\Exception\FileInUseException;
14
use Icewind\SMB\Exception\InvalidTypeException;
15
use Icewind\SMB\Exception\NotFoundException;
16
use Icewind\SMB\INotifyHandler;
17
use Icewind\SMB\IServer;
18
use Icewind\SMB\System;
19
use Icewind\SMB\TimeZoneProvider;
20
use Icewind\Streams\CallbackWrapper;
21
22
class Share extends AbstractShare {
23
	/**
24
	 * @var IServer $server
25
	 */
26
	private $server;
27
28
	/**
29
	 * @var string $name
30
	 */
31
	private $name;
32
33
	/**
34
	 * @var Connection $connection
35
	 */
36
	public $connection;
37
38
	/**
39
	 * @var Parser
40
	 */
41
	protected $parser;
42
43
	/**
44
	 * @var System
45
	 */
46
	private $system;
47
48
	/**
49
	 * @param IServer $server
50
	 * @param string $name
51
	 * @param System $system
52
	 */
53 255
	public function __construct(IServer $server, $name, System $system = null) {
54 255
		parent::__construct();
55 255
		$this->server = $server;
56 255
		$this->name = $name;
57 255
		$this->system = (!is_null($system)) ? $system : new System();
58 255
		$this->parser = new Parser(new TimeZoneProvider($this->server->getHost(), $this->system));
59 255
	}
60
61 255
	private function getAuthFileArgument() {
62 255
		if ($this->server->getAuth()->getUsername()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->server->getAuth()->getUsername() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
63 255
			return '--authentication-file=' . System::getFD(3);
64
		} else {
65
			return '';
66
		}
67
	}
68
69 255
	protected function getConnection() {
70 255
		$command = sprintf('%s%s %s %s %s',
71 255
			$this->system->hasStdBuf() ? 'stdbuf -o0 ' : '',
72 255
			$this->system->getSmbclientPath(),
73 255
			$this->getAuthFileArgument(),
74 255
			$this->server->getAuth()->getExtraCommandLineArguments(),
75 255
			escapeshellarg('//' . $this->server->getHost() . '/' . $this->name)
76 255
		);
77 255
		$connection = new Connection($command, $this->parser);
78 255
		$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
79 255
		$connection->connect();
80 255
		if (!$connection->isValid()) {
81
			throw new ConnectionException($connection->readLine());
82
		}
83
		// some versions of smbclient add a help message in first of the first prompt
84 255
		$connection->clearTillPrompt();
85 255
		return $connection;
86
	}
87
88
	/**
89
	 * @throws \Icewind\SMB\Exception\ConnectionException
90
	 * @throws \Icewind\SMB\Exception\AuthenticationException
91
	 * @throws \Icewind\SMB\Exception\InvalidHostException
92
	 */
93 254
	protected function connect() {
94 254
		if ($this->connection and $this->connection->isValid()) {
95 254
			return;
96
		}
97 254
		$this->connection = $this->getConnection();
98 254
	}
99
100 1
	protected function reconnect() {
101 1
		$this->connection->reconnect();
102 1
		if (!$this->connection->isValid()) {
103
			throw new ConnectionException();
104
		}
105 1
	}
106
107
	/**
108
	 * Get the name of the share
109
	 *
110
	 * @return string
111
	 */
112
	public function getName() {
113
		return $this->name;
114
	}
115
116 254
	protected function simpleCommand($command, $path) {
117 254
		$escapedPath = $this->escapePath($path);
118 254
		$cmd = $command . ' ' . $escapedPath;
119 254
		$output = $this->execute($cmd);
120 254
		return $this->parseOutput($output, $path);
121
	}
122
123
	/**
124
	 * List the content of a remote folder
125
	 *
126
	 * @param $path
127
	 * @return \Icewind\SMB\IFileInfo[]
128
	 *
129
	 * @throws \Icewind\SMB\Exception\NotFoundException
130
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
131
	 */
132 250
	public function dir($path) {
133 250
		$escapedPath = $this->escapePath($path);
134 250
		$output = $this->execute('cd ' . $escapedPath);
135
		//check output for errors
136 250
		$this->parseOutput($output, $path);
137 250
		$output = $this->execute('dir');
138
139 250
		$this->execute('cd /');
140
141 250
		return $this->parser->parseDir($output, $path);
142
	}
143
144
	/**
145
	 * @param string $path
146
	 * @return \Icewind\SMB\IFileInfo
147
	 */
148 33
	public function stat($path) {
149 33
		$escapedPath = $this->escapePath($path);
150 24
		$output = $this->execute('allinfo ' . $escapedPath);
151
		// Windows and non Windows Fileserver may respond different
152
		// to the allinfo command for directories. If the result is a single
153
		// line = error line, redo it with a different allinfo parameter
154 24
		if ($escapedPath == '""' && count($output) < 2) {
155
			$output = $this->execute('allinfo ' . '"."');
156
		}
157 24
		if (count($output) < 3) {
158 1
			$this->parseOutput($output, $path);
159
		}
160 23
		$stat = $this->parser->parseStat($output);
161 23
		return new FileInfo($path, basename($path), $stat['size'], $stat['mtime'], $stat['mode']);
162
	}
163
164
	/**
165
	 * Create a folder on the share
166
	 *
167
	 * @param string $path
168
	 * @return bool
169
	 *
170
	 * @throws \Icewind\SMB\Exception\NotFoundException
171
	 * @throws \Icewind\SMB\Exception\AlreadyExistsException
172
	 */
173 251
	public function mkdir($path) {
174 251
		return $this->simpleCommand('mkdir', $path);
175
	}
176
177
	/**
178
	 * Remove a folder on the share
179
	 *
180
	 * @param string $path
181
	 * @return bool
182
	 *
183
	 * @throws \Icewind\SMB\Exception\NotFoundException
184
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
185
	 */
186 251
	public function rmdir($path) {
187 251
		return $this->simpleCommand('rmdir', $path);
188
	}
189
190
	/**
191
	 * Delete a file on the share
192
	 *
193
	 * @param string $path
194
	 * @param bool $secondTry
195
	 * @return bool
196
	 * @throws InvalidTypeException
197
	 * @throws NotFoundException
198
	 * @throws \Exception
199
	 */
200 131
	public function del($path, $secondTry = false) {
201
		//del return a file not found error when trying to delete a folder
202
		//we catch it so we can check if $path doesn't exist or is of invalid type
203
		try {
204 131
			return $this->simpleCommand('del', $path);
205 12
		} catch (NotFoundException $e) {
206
			//no need to do anything with the result, we just check if this throws the not found error
207
			try {
208 2
				$this->simpleCommand('ls', $path);
209 2
			} catch (NotFoundException $e2) {
210 1
				throw $e;
211 1
			} catch (\Exception $e2) {
212 1
				throw new InvalidTypeException($path);
213
			}
214
			throw $e;
215 10
		} catch (FileInUseException $e) {
216 1
			if ($secondTry) {
217
				throw $e;
218
			}
219 1
			$this->reconnect();
220 1
			return $this->del($path, true);
221
		}
222
	}
223
224
	/**
225
	 * Rename a remote file
226
	 *
227
	 * @param string $from
228
	 * @param string $to
229
	 * @return bool
230
	 *
231
	 * @throws \Icewind\SMB\Exception\NotFoundException
232
	 * @throws \Icewind\SMB\Exception\AlreadyExistsException
233
	 */
234 30 View Code Duplication
	public function rename($from, $to) {
235 30
		$path1 = $this->escapePath($from);
236 21
		$path2 = $this->escapePath($to);
237 21
		$output = $this->execute('rename ' . $path1 . ' ' . $path2);
238 21
		return $this->parseOutput($output, $to);
239
	}
240
241
	/**
242
	 * Upload a local file
243
	 *
244
	 * @param string $source local file
245
	 * @param string $target remove file
246
	 * @return bool
247
	 *
248
	 * @throws \Icewind\SMB\Exception\NotFoundException
249
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
250
	 */
251 115 View Code Duplication
	public function put($source, $target) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
252 115
		$path1 = $this->escapeLocalPath($source); //first path is local, needs different escaping
253 115
		$path2 = $this->escapePath($target);
254 106
		$output = $this->execute('put ' . $path1 . ' ' . $path2);
255 106
		return $this->parseOutput($output, $target);
256
	}
257
258
	/**
259
	 * Download a remote file
260
	 *
261
	 * @param string $source remove file
262
	 * @param string $target local file
263
	 * @return bool
264
	 *
265
	 * @throws \Icewind\SMB\Exception\NotFoundException
266
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
267
	 */
268 44 View Code Duplication
	public function get($source, $target) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
269 44
		$path1 = $this->escapePath($source);
270 35
		$path2 = $this->escapeLocalPath($target); //second path is local, needs different escaping
271 35
		$output = $this->execute('get ' . $path1 . ' ' . $path2);
272 35
		return $this->parseOutput($output, $source);
273
	}
274
275
	/**
276
	 * Open a readable stream to a remote file
277
	 *
278
	 * @param string $source
279
	 * @return resource a read only stream with the contents of the remote file
280
	 *
281
	 * @throws \Icewind\SMB\Exception\NotFoundException
282
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
283
	 */
284 25
	public function read($source) {
285 25
		$source = $this->escapePath($source);
286
		// since returned stream is closed by the caller we need to create a new instance
287
		// since we can't re-use the same file descriptor over multiple calls
288 16
		$connection = $this->getConnection();
289
290 16
		$connection->write('get ' . $source . ' ' . System::getFD(5));
291 16
		$connection->write('exit');
292 16
		$fh = $connection->getFileOutputStream();
293 16
		stream_context_set_option($fh, 'file', 'connection', $connection);
294 16
		return $fh;
295
	}
296
297
	/**
298
	 * Open a writable stream to a remote file
299
	 *
300
	 * @param string $target
301
	 * @return resource a write only stream to upload a remote file
302
	 *
303
	 * @throws \Icewind\SMB\Exception\NotFoundException
304
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
305
	 */
306 25
	public function write($target) {
307 25
		$target = $this->escapePath($target);
308
		// since returned stream is closed by the caller we need to create a new instance
309
		// since we can't re-use the same file descriptor over multiple calls
310 16
		$connection = $this->getConnection();
311
312 16
		$fh = $connection->getFileInputStream();
313 16
		$connection->write('put ' . System::getFD(4) . ' ' . $target);
314 16
		$connection->write('exit');
315
316
		// use a close callback to ensure the upload is finished before continuing
317
		// this also serves as a way to keep the connection in scope
318 16
		return CallbackWrapper::wrap($fh, null, null, function () use ($connection, $target) {
319 16
			$connection->close(false); // dont terminate, give the upload some time
320 16
		});
321
	}
322
323
	/**
324
	 * @param string $path
325
	 * @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
326
	 * @return mixed
327
	 */
328 8
	public function setMode($path, $mode) {
329 8
		$modeString = '';
330
		$modeMap = array(
331 8
			FileInfo::MODE_READONLY => 'r',
332 8
			FileInfo::MODE_HIDDEN   => 'h',
333 8
			FileInfo::MODE_ARCHIVE  => 'a',
334 8
			FileInfo::MODE_SYSTEM   => 's'
335 8
		);
336 8
		foreach ($modeMap as $modeByte => $string) {
337 8
			if ($mode & $modeByte) {
338 8
				$modeString .= $string;
339 8
			}
340 8
		}
341 8
		$path = $this->escapePath($path);
342
343
		// first reset the mode to normal
344 8
		$cmd = 'setmode ' . $path . ' -rsha';
345 8
		$output = $this->execute($cmd);
346 8
		$this->parseOutput($output, $path);
347
348 8
		if ($mode !== FileInfo::MODE_NORMAL) {
349
			// then set the modes we want
350 8
			$cmd = 'setmode ' . $path . ' ' . $modeString;
351 8
			$output = $this->execute($cmd);
352 8
			return $this->parseOutput($output, $path);
353
		} else {
354 8
			return true;
355
		}
356
	}
357
358
	/**
359
	 * @param string $path
360
	 * @return INotifyHandler
361
	 * @throws ConnectionException
362
	 * @throws DependencyException
363
	 */
364 5
	public function notify($path) {
365 5
		if (!$this->system->hasStdBuf()) { //stdbuf is required to disable smbclient's output buffering
366
			throw new DependencyException('stdbuf is required for usage of the notify command');
367
		}
368 5
		$connection = $this->getConnection(); // use a fresh connection since the notify command blocks the process
369 5
		$command = 'notify ' . $this->escapePath($path);
370 5
		$connection->write($command . PHP_EOL);
371 5
		return new NotifyHandler($connection, $path);
372
	}
373
374
	/**
375
	 * @param string $command
376
	 * @return array
377
	 */
378 254
	protected function execute($command) {
379 254
		$this->connect();
380 254
		$this->connection->write($command . PHP_EOL);
381 254
		$output = $this->connection->read();
382 254
		return $output;
383
	}
384
385
	/**
386
	 * check output for errors
387
	 *
388
	 * @param string[] $lines
389
	 * @param string $path
390
	 *
391
	 * @throws NotFoundException
392
	 * @throws \Icewind\SMB\Exception\AlreadyExistsException
393
	 * @throws \Icewind\SMB\Exception\AccessDeniedException
394
	 * @throws \Icewind\SMB\Exception\NotEmptyException
395
	 * @throws \Icewind\SMB\Exception\InvalidTypeException
396
	 * @throws \Icewind\SMB\Exception\Exception
397
	 * @return bool
398
	 */
399 254
	protected function parseOutput($lines, $path = '') {
400 254
		if (count($lines) === 0) {
401 254
			return true;
402
		} else {
403 20
			$this->parser->checkForError($lines, $path);
404
			return false;
405
		}
406
	}
407
408
	/**
409
	 * @param string $string
410
	 * @return string
411
	 */
412
	protected function escape($string) {
413
		return escapeshellarg($string);
414
	}
415
416
	/**
417
	 * @param string $path
418
	 * @return string
419
	 */
420 255
	protected function escapePath($path) {
421 255
		$this->verifyPath($path);
422 255
		if ($path === '/') {
423 1
			$path = '';
424 1
		}
425 255
		$path = str_replace('/', '\\', $path);
426 255
		$path = str_replace('"', '^"', $path);
427 255
		$path = ltrim($path, '\\');
428 255
		return '"' . $path . '"';
429
	}
430
431
	/**
432
	 * @param string $path
433
	 * @return string
434
	 */
435 133
	protected function escapeLocalPath($path) {
436 133
		$path = str_replace('"', '\"', $path);
437 133
		return '"' . $path . '"';
438
	}
439
440 255
	public function __destruct() {
441 255
		unset($this->connection);
442 255
	}
443
}
444