Completed
Push — master ( c92ae0...dc7863 )
by Robin
03:57
created

Share::simpleCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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