|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* @copyright Copyright (c) 2016, ownCloud, Inc. |
|
4
|
|
|
* |
|
5
|
|
|
* @author aler9 <[email protected]> |
|
6
|
|
|
* @author Arthur Schiwon <[email protected]> |
|
7
|
|
|
* @author Bart Visscher <[email protected]> |
|
8
|
|
|
* @author Boris Rybalkin <[email protected]> |
|
9
|
|
|
* @author Brice Maron <[email protected]> |
|
10
|
|
|
* @author Christoph Wurst <[email protected]> |
|
11
|
|
|
* @author J0WI <[email protected]> |
|
12
|
|
|
* @author Jakob Sack <[email protected]> |
|
13
|
|
|
* @author Joas Schilling <[email protected]> |
|
14
|
|
|
* @author Johannes Leuker <[email protected]> |
|
15
|
|
|
* @author Jörn Friedrich Dreyer <[email protected]> |
|
16
|
|
|
* @author Klaas Freitag <[email protected]> |
|
17
|
|
|
* @author Lukas Reschke <[email protected]> |
|
18
|
|
|
* @author Michael Gapczynski <[email protected]> |
|
19
|
|
|
* @author Morris Jobke <[email protected]> |
|
20
|
|
|
* @author Robin Appelman <[email protected]> |
|
21
|
|
|
* @author Roeland Jago Douma <[email protected]> |
|
22
|
|
|
* @author Sjors van der Pluijm <[email protected]> |
|
23
|
|
|
* @author Stefan Weil <[email protected]> |
|
24
|
|
|
* @author Thomas Müller <[email protected]> |
|
25
|
|
|
* @author Tigran Mkrtchyan <[email protected]> |
|
26
|
|
|
* @author Vincent Petry <[email protected]> |
|
27
|
|
|
* |
|
28
|
|
|
* @license AGPL-3.0 |
|
29
|
|
|
* |
|
30
|
|
|
* This code is free software: you can redistribute it and/or modify |
|
31
|
|
|
* it under the terms of the GNU Affero General Public License, version 3, |
|
32
|
|
|
* as published by the Free Software Foundation. |
|
33
|
|
|
* |
|
34
|
|
|
* This program is distributed in the hope that it will be useful, |
|
35
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
36
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
37
|
|
|
* GNU Affero General Public License for more details. |
|
38
|
|
|
* |
|
39
|
|
|
* You should have received a copy of the GNU Affero General Public License, version 3, |
|
40
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|
41
|
|
|
* |
|
42
|
|
|
*/ |
|
43
|
|
|
namespace OC\Files\Storage; |
|
44
|
|
|
|
|
45
|
|
|
use OC\Files\Filesystem; |
|
46
|
|
|
use OC\Files\Storage\Wrapper\Jail; |
|
47
|
|
|
use OCP\Constants; |
|
48
|
|
|
use OCP\Files\ForbiddenException; |
|
49
|
|
|
use OCP\Files\GenericFileException; |
|
50
|
|
|
use OCP\Files\IMimeTypeDetector; |
|
51
|
|
|
use OCP\Files\Storage\IStorage; |
|
52
|
|
|
use OCP\IConfig; |
|
53
|
|
|
use OCP\ILogger; |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* for local filestore, we only have to map the paths |
|
57
|
|
|
*/ |
|
58
|
|
|
class Local extends \OC\Files\Storage\Common { |
|
59
|
|
|
protected $datadir; |
|
60
|
|
|
|
|
61
|
|
|
protected $dataDirLength; |
|
62
|
|
|
|
|
63
|
|
|
protected $realDataDir; |
|
64
|
|
|
|
|
65
|
|
|
private IConfig $config; |
|
66
|
|
|
|
|
67
|
|
|
private IMimeTypeDetector $mimeTypeDetector; |
|
68
|
|
|
|
|
69
|
|
|
public function __construct($arguments) { |
|
70
|
|
|
if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) { |
|
71
|
|
|
throw new \InvalidArgumentException('No data directory set for local storage'); |
|
72
|
|
|
} |
|
73
|
|
|
$this->datadir = str_replace('//', '/', $arguments['datadir']); |
|
74
|
|
|
// some crazy code uses a local storage on root... |
|
75
|
|
|
if ($this->datadir === '/') { |
|
76
|
|
|
$this->realDataDir = $this->datadir; |
|
77
|
|
|
} else { |
|
78
|
|
|
$realPath = realpath($this->datadir) ?: $this->datadir; |
|
79
|
|
|
$this->realDataDir = rtrim($realPath, '/') . '/'; |
|
80
|
|
|
} |
|
81
|
|
|
if (substr($this->datadir, -1) !== '/') { |
|
82
|
|
|
$this->datadir .= '/'; |
|
83
|
|
|
} |
|
84
|
|
|
$this->dataDirLength = strlen($this->realDataDir); |
|
85
|
|
|
$this->config = \OC::$server->get(IConfig::class); |
|
86
|
|
|
$this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class); |
|
87
|
|
|
} |
|
88
|
|
|
|
|
89
|
|
|
public function __destruct() { |
|
90
|
|
|
} |
|
91
|
|
|
|
|
92
|
|
|
public function getId() { |
|
93
|
|
|
return 'local::' . $this->datadir; |
|
94
|
|
|
} |
|
95
|
|
|
|
|
96
|
|
|
public function mkdir($path) { |
|
97
|
|
|
$sourcePath = $this->getSourcePath($path); |
|
98
|
|
|
$oldMask = umask(022); |
|
99
|
|
|
$result = @mkdir($sourcePath, 0777, true); |
|
100
|
|
|
umask($oldMask); |
|
101
|
|
|
return $result; |
|
102
|
|
|
} |
|
103
|
|
|
|
|
104
|
|
|
public function rmdir($path) { |
|
105
|
|
|
if (!$this->isDeletable($path)) { |
|
106
|
|
|
return false; |
|
107
|
|
|
} |
|
108
|
|
|
try { |
|
109
|
|
|
$it = new \RecursiveIteratorIterator( |
|
110
|
|
|
new \RecursiveDirectoryIterator($this->getSourcePath($path)), |
|
111
|
|
|
\RecursiveIteratorIterator::CHILD_FIRST |
|
112
|
|
|
); |
|
113
|
|
|
/** |
|
114
|
|
|
* RecursiveDirectoryIterator on an NFS path isn't iterable with foreach |
|
115
|
|
|
* This bug is fixed in PHP 5.5.9 or before |
|
116
|
|
|
* See #8376 |
|
117
|
|
|
*/ |
|
118
|
|
|
$it->rewind(); |
|
119
|
|
|
while ($it->valid()) { |
|
120
|
|
|
/** |
|
121
|
|
|
* @var \SplFileInfo $file |
|
122
|
|
|
*/ |
|
123
|
|
|
$file = $it->current(); |
|
124
|
|
|
clearstatcache(true, $this->getSourcePath($file)); |
|
125
|
|
|
if (in_array($file->getBasename(), ['.', '..'])) { |
|
126
|
|
|
$it->next(); |
|
127
|
|
|
continue; |
|
128
|
|
|
} elseif ($file->isDir()) { |
|
129
|
|
|
rmdir($file->getPathname()); |
|
130
|
|
|
} elseif ($file->isFile() || $file->isLink()) { |
|
131
|
|
|
unlink($file->getPathname()); |
|
132
|
|
|
} |
|
133
|
|
|
$it->next(); |
|
134
|
|
|
} |
|
135
|
|
|
clearstatcache(true, $this->getSourcePath($path)); |
|
136
|
|
|
return rmdir($this->getSourcePath($path)); |
|
137
|
|
|
} catch (\UnexpectedValueException $e) { |
|
138
|
|
|
return false; |
|
139
|
|
|
} |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
public function opendir($path) { |
|
143
|
|
|
return opendir($this->getSourcePath($path)); |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
public function is_dir($path) { |
|
147
|
|
|
if (substr($path, -1) == '/') { |
|
148
|
|
|
$path = substr($path, 0, -1); |
|
149
|
|
|
} |
|
150
|
|
|
return is_dir($this->getSourcePath($path)); |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
public function is_file($path) { |
|
154
|
|
|
return is_file($this->getSourcePath($path)); |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
public function stat($path) { |
|
158
|
|
|
$fullPath = $this->getSourcePath($path); |
|
159
|
|
|
clearstatcache(true, $fullPath); |
|
160
|
|
|
$statResult = @stat($fullPath); |
|
161
|
|
|
if (PHP_INT_SIZE === 4 && $statResult && !$this->is_dir($path)) { |
|
162
|
|
|
$filesize = $this->filesize($path); |
|
163
|
|
|
$statResult['size'] = $filesize; |
|
164
|
|
|
$statResult[7] = $filesize; |
|
165
|
|
|
} |
|
166
|
|
|
if (is_array($statResult)) { |
|
167
|
|
|
$statResult['full_path'] = $fullPath; |
|
168
|
|
|
} |
|
169
|
|
|
return $statResult; |
|
170
|
|
|
} |
|
171
|
|
|
|
|
172
|
|
|
/** |
|
173
|
|
|
* @inheritdoc |
|
174
|
|
|
*/ |
|
175
|
|
|
public function getMetaData($path) { |
|
176
|
|
|
try { |
|
177
|
|
|
$stat = $this->stat($path); |
|
178
|
|
|
} catch (ForbiddenException $e) { |
|
179
|
|
|
return null; |
|
180
|
|
|
} |
|
181
|
|
|
if (!$stat) { |
|
182
|
|
|
return null; |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
$permissions = Constants::PERMISSION_SHARE; |
|
186
|
|
|
$statPermissions = $stat['mode']; |
|
187
|
|
|
$isDir = ($statPermissions & 0x4000) === 0x4000 && !($statPermissions & 0x8000); |
|
188
|
|
|
if ($statPermissions & 0x0100) { |
|
189
|
|
|
$permissions += Constants::PERMISSION_READ; |
|
190
|
|
|
} |
|
191
|
|
|
if ($statPermissions & 0x0080) { |
|
192
|
|
|
$permissions += Constants::PERMISSION_UPDATE; |
|
193
|
|
|
if ($isDir) { |
|
194
|
|
|
$permissions += Constants::PERMISSION_CREATE; |
|
195
|
|
|
} |
|
196
|
|
|
} |
|
197
|
|
|
|
|
198
|
|
|
if (!($path === '' || $path === '/')) { // deletable depends on the parents unix permissions |
|
199
|
|
|
$parent = dirname($stat['full_path']); |
|
200
|
|
|
if (is_writable($parent)) { |
|
201
|
|
|
$permissions += Constants::PERMISSION_DELETE; |
|
202
|
|
|
} |
|
203
|
|
|
} |
|
204
|
|
|
|
|
205
|
|
|
$data = []; |
|
206
|
|
|
$data['mimetype'] = $isDir ? 'httpd/unix-directory' : $this->mimeTypeDetector->detectPath($path); |
|
207
|
|
|
$data['mtime'] = $stat['mtime']; |
|
208
|
|
|
if ($data['mtime'] === false) { |
|
209
|
|
|
$data['mtime'] = time(); |
|
210
|
|
|
} |
|
211
|
|
|
if ($isDir) { |
|
212
|
|
|
$data['size'] = -1; //unknown |
|
213
|
|
|
} else { |
|
214
|
|
|
$data['size'] = $stat['size']; |
|
215
|
|
|
} |
|
216
|
|
|
$data['etag'] = $this->calculateEtag($path, $stat); |
|
217
|
|
|
$data['storage_mtime'] = $data['mtime']; |
|
218
|
|
|
$data['permissions'] = $permissions; |
|
219
|
|
|
$data['name'] = basename($path); |
|
220
|
|
|
|
|
221
|
|
|
return $data; |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
|
|
public function filetype($path) { |
|
225
|
|
|
$filetype = filetype($this->getSourcePath($path)); |
|
226
|
|
|
if ($filetype == 'link') { |
|
227
|
|
|
$filetype = filetype(realpath($this->getSourcePath($path))); |
|
228
|
|
|
} |
|
229
|
|
|
return $filetype; |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
public function filesize($path) { |
|
233
|
|
|
if (!$this->is_file($path)) { |
|
234
|
|
|
return 0; |
|
235
|
|
|
} |
|
236
|
|
|
$fullPath = $this->getSourcePath($path); |
|
237
|
|
|
if (PHP_INT_SIZE === 4) { |
|
238
|
|
|
$helper = new \OC\LargeFileHelper; |
|
239
|
|
|
return $helper->getFileSize($fullPath); |
|
240
|
|
|
} |
|
241
|
|
|
return filesize($fullPath); |
|
242
|
|
|
} |
|
243
|
|
|
|
|
244
|
|
|
public function isReadable($path) { |
|
245
|
|
|
return is_readable($this->getSourcePath($path)); |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
public function isUpdatable($path) { |
|
249
|
|
|
return is_writable($this->getSourcePath($path)); |
|
250
|
|
|
} |
|
251
|
|
|
|
|
252
|
|
|
public function file_exists($path) { |
|
253
|
|
|
return file_exists($this->getSourcePath($path)); |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
public function filemtime($path) { |
|
257
|
|
|
$fullPath = $this->getSourcePath($path); |
|
258
|
|
|
clearstatcache(true, $fullPath); |
|
259
|
|
|
if (!$this->file_exists($path)) { |
|
260
|
|
|
return false; |
|
261
|
|
|
} |
|
262
|
|
|
if (PHP_INT_SIZE === 4) { |
|
263
|
|
|
$helper = new \OC\LargeFileHelper(); |
|
264
|
|
|
return $helper->getFileMtime($fullPath); |
|
265
|
|
|
} |
|
266
|
|
|
return filemtime($fullPath); |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
public function touch($path, $mtime = null) { |
|
270
|
|
|
// sets the modification time of the file to the given value. |
|
271
|
|
|
// If mtime is nil the current time is set. |
|
272
|
|
|
// note that the access time of the file always changes to the current time. |
|
273
|
|
|
if ($this->file_exists($path) and !$this->isUpdatable($path)) { |
|
274
|
|
|
return false; |
|
275
|
|
|
} |
|
276
|
|
|
$oldMask = umask(022); |
|
277
|
|
|
if (!is_null($mtime)) { |
|
278
|
|
|
$result = @touch($this->getSourcePath($path), $mtime); |
|
279
|
|
|
} else { |
|
280
|
|
|
$result = @touch($this->getSourcePath($path)); |
|
281
|
|
|
} |
|
282
|
|
|
umask($oldMask); |
|
283
|
|
|
if ($result) { |
|
284
|
|
|
clearstatcache(true, $this->getSourcePath($path)); |
|
285
|
|
|
} |
|
286
|
|
|
|
|
287
|
|
|
return $result; |
|
288
|
|
|
} |
|
289
|
|
|
|
|
290
|
|
|
public function file_get_contents($path) { |
|
291
|
|
|
return file_get_contents($this->getSourcePath($path)); |
|
292
|
|
|
} |
|
293
|
|
|
|
|
294
|
|
|
public function file_put_contents($path, $data) { |
|
295
|
|
|
$oldMask = umask(022); |
|
296
|
|
|
$result = file_put_contents($this->getSourcePath($path), $data); |
|
297
|
|
|
umask($oldMask); |
|
298
|
|
|
return $result; |
|
299
|
|
|
} |
|
300
|
|
|
|
|
301
|
|
|
public function unlink($path) { |
|
302
|
|
|
if ($this->is_dir($path)) { |
|
303
|
|
|
return $this->rmdir($path); |
|
304
|
|
|
} elseif ($this->is_file($path)) { |
|
305
|
|
|
return unlink($this->getSourcePath($path)); |
|
306
|
|
|
} else { |
|
307
|
|
|
return false; |
|
308
|
|
|
} |
|
309
|
|
|
} |
|
310
|
|
|
|
|
311
|
|
|
private function checkTreeForForbiddenItems(string $path) { |
|
312
|
|
|
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)); |
|
313
|
|
|
foreach ($iterator as $file) { |
|
314
|
|
|
/** @var \SplFileInfo $file */ |
|
315
|
|
|
if (Filesystem::isFileBlacklisted($file->getBasename())) { |
|
316
|
|
|
throw new ForbiddenException('Invalid path: ' . $file->getPathname(), false); |
|
317
|
|
|
} |
|
318
|
|
|
} |
|
319
|
|
|
} |
|
320
|
|
|
|
|
321
|
|
|
public function rename($path1, $path2) { |
|
322
|
|
|
$srcParent = dirname($path1); |
|
323
|
|
|
$dstParent = dirname($path2); |
|
324
|
|
|
|
|
325
|
|
|
if (!$this->isUpdatable($srcParent)) { |
|
326
|
|
|
\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR); |
|
|
|
|
|
|
327
|
|
|
return false; |
|
328
|
|
|
} |
|
329
|
|
|
|
|
330
|
|
|
if (!$this->isUpdatable($dstParent)) { |
|
331
|
|
|
\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR); |
|
|
|
|
|
|
332
|
|
|
return false; |
|
333
|
|
|
} |
|
334
|
|
|
|
|
335
|
|
|
if (!$this->file_exists($path1)) { |
|
336
|
|
|
\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR); |
|
|
|
|
|
|
337
|
|
|
return false; |
|
338
|
|
|
} |
|
339
|
|
|
|
|
340
|
|
|
if ($this->is_dir($path2)) { |
|
341
|
|
|
$this->rmdir($path2); |
|
342
|
|
|
} elseif ($this->is_file($path2)) { |
|
343
|
|
|
$this->unlink($path2); |
|
344
|
|
|
} |
|
345
|
|
|
|
|
346
|
|
|
if ($this->is_dir($path1)) { |
|
347
|
|
|
// we can't move folders across devices, use copy instead |
|
348
|
|
|
$stat1 = stat(dirname($this->getSourcePath($path1))); |
|
349
|
|
|
$stat2 = stat(dirname($this->getSourcePath($path2))); |
|
350
|
|
|
if ($stat1['dev'] !== $stat2['dev']) { |
|
351
|
|
|
$result = $this->copy($path1, $path2); |
|
352
|
|
|
if ($result) { |
|
353
|
|
|
$result &= $this->rmdir($path1); |
|
354
|
|
|
} |
|
355
|
|
|
return $result; |
|
|
|
|
|
|
356
|
|
|
} |
|
357
|
|
|
|
|
358
|
|
|
$this->checkTreeForForbiddenItems($this->getSourcePath($path1)); |
|
359
|
|
|
} |
|
360
|
|
|
|
|
361
|
|
|
return rename($this->getSourcePath($path1), $this->getSourcePath($path2)); |
|
362
|
|
|
} |
|
363
|
|
|
|
|
364
|
|
|
public function copy($path1, $path2) { |
|
365
|
|
|
if ($this->is_dir($path1)) { |
|
366
|
|
|
return parent::copy($path1, $path2); |
|
367
|
|
|
} else { |
|
368
|
|
|
$oldMask = umask(022); |
|
369
|
|
|
$result = copy($this->getSourcePath($path1), $this->getSourcePath($path2)); |
|
370
|
|
|
umask($oldMask); |
|
371
|
|
|
return $result; |
|
372
|
|
|
} |
|
373
|
|
|
} |
|
374
|
|
|
|
|
375
|
|
|
public function fopen($path, $mode) { |
|
376
|
|
|
$oldMask = umask(022); |
|
377
|
|
|
$result = fopen($this->getSourcePath($path), $mode); |
|
378
|
|
|
umask($oldMask); |
|
379
|
|
|
return $result; |
|
380
|
|
|
} |
|
381
|
|
|
|
|
382
|
|
|
public function hash($type, $path, $raw = false) { |
|
383
|
|
|
return hash_file($type, $this->getSourcePath($path), $raw); |
|
384
|
|
|
} |
|
385
|
|
|
|
|
386
|
|
|
public function free_space($path) { |
|
387
|
|
|
$sourcePath = $this->getSourcePath($path); |
|
388
|
|
|
// using !is_dir because $sourcePath might be a part file or |
|
389
|
|
|
// non-existing file, so we'd still want to use the parent dir |
|
390
|
|
|
// in such cases |
|
391
|
|
|
if (!is_dir($sourcePath)) { |
|
392
|
|
|
// disk_free_space doesn't work on files |
|
393
|
|
|
$sourcePath = dirname($sourcePath); |
|
394
|
|
|
} |
|
395
|
|
|
$space = function_exists('disk_free_space') ? disk_free_space($sourcePath) : false; |
|
396
|
|
|
if ($space === false || is_null($space)) { |
|
397
|
|
|
return \OCP\Files\FileInfo::SPACE_UNKNOWN; |
|
398
|
|
|
} |
|
399
|
|
|
return $space; |
|
400
|
|
|
} |
|
401
|
|
|
|
|
402
|
|
|
public function search($query) { |
|
403
|
|
|
return $this->searchInDir($query); |
|
404
|
|
|
} |
|
405
|
|
|
|
|
406
|
|
|
public function getLocalFile($path) { |
|
407
|
|
|
return $this->getSourcePath($path); |
|
408
|
|
|
} |
|
409
|
|
|
|
|
410
|
|
|
public function getLocalFolder($path) { |
|
411
|
|
|
return $this->getSourcePath($path); |
|
412
|
|
|
} |
|
413
|
|
|
|
|
414
|
|
|
/** |
|
415
|
|
|
* @param string $query |
|
416
|
|
|
* @param string $dir |
|
417
|
|
|
* @return array |
|
418
|
|
|
*/ |
|
419
|
|
|
protected function searchInDir($query, $dir = '') { |
|
420
|
|
|
$files = []; |
|
421
|
|
|
$physicalDir = $this->getSourcePath($dir); |
|
422
|
|
|
foreach (scandir($physicalDir) as $item) { |
|
423
|
|
|
if (\OC\Files\Filesystem::isIgnoredDir($item)) { |
|
424
|
|
|
continue; |
|
425
|
|
|
} |
|
426
|
|
|
$physicalItem = $physicalDir . '/' . $item; |
|
427
|
|
|
|
|
428
|
|
|
if (strstr(strtolower($item), strtolower($query)) !== false) { |
|
429
|
|
|
$files[] = $dir . '/' . $item; |
|
430
|
|
|
} |
|
431
|
|
|
if (is_dir($physicalItem)) { |
|
432
|
|
|
$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item)); |
|
433
|
|
|
} |
|
434
|
|
|
} |
|
435
|
|
|
return $files; |
|
436
|
|
|
} |
|
437
|
|
|
|
|
438
|
|
|
/** |
|
439
|
|
|
* check if a file or folder has been updated since $time |
|
440
|
|
|
* |
|
441
|
|
|
* @param string $path |
|
442
|
|
|
* @param int $time |
|
443
|
|
|
* @return bool |
|
444
|
|
|
*/ |
|
445
|
|
|
public function hasUpdated($path, $time) { |
|
446
|
|
|
if ($this->file_exists($path)) { |
|
447
|
|
|
return $this->filemtime($path) > $time; |
|
448
|
|
|
} else { |
|
449
|
|
|
return true; |
|
450
|
|
|
} |
|
451
|
|
|
} |
|
452
|
|
|
|
|
453
|
|
|
/** |
|
454
|
|
|
* Get the source path (on disk) of a given path |
|
455
|
|
|
* |
|
456
|
|
|
* @param string $path |
|
457
|
|
|
* @return string |
|
458
|
|
|
* @throws ForbiddenException |
|
459
|
|
|
*/ |
|
460
|
|
|
public function getSourcePath($path) { |
|
461
|
|
|
if (Filesystem::isFileBlacklisted($path)) { |
|
462
|
|
|
throw new ForbiddenException('Invalid path: ' . $path, false); |
|
463
|
|
|
} |
|
464
|
|
|
|
|
465
|
|
|
$fullPath = $this->datadir . $path; |
|
466
|
|
|
$currentPath = $path; |
|
467
|
|
|
$allowSymlinks = $this->config->getSystemValue('localstorage.allowsymlinks', false); |
|
468
|
|
|
if ($allowSymlinks || $currentPath === '') { |
|
469
|
|
|
return $fullPath; |
|
470
|
|
|
} |
|
471
|
|
|
$pathToResolve = $fullPath; |
|
472
|
|
|
$realPath = realpath($pathToResolve); |
|
473
|
|
|
while ($realPath === false) { // for non existing files check the parent directory |
|
474
|
|
|
$currentPath = dirname($currentPath); |
|
475
|
|
|
if ($currentPath === '' || $currentPath === '.') { |
|
476
|
|
|
return $fullPath; |
|
477
|
|
|
} |
|
478
|
|
|
$realPath = realpath($this->datadir . $currentPath); |
|
479
|
|
|
} |
|
480
|
|
|
if ($realPath) { |
|
481
|
|
|
$realPath = $realPath . '/'; |
|
482
|
|
|
} |
|
483
|
|
|
if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) { |
|
484
|
|
|
return $fullPath; |
|
485
|
|
|
} |
|
486
|
|
|
|
|
487
|
|
|
\OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ILogger::ERROR); |
|
|
|
|
|
|
488
|
|
|
throw new ForbiddenException('Following symlinks is not allowed', false); |
|
489
|
|
|
} |
|
490
|
|
|
|
|
491
|
|
|
/** |
|
492
|
|
|
* {@inheritdoc} |
|
493
|
|
|
*/ |
|
494
|
|
|
public function isLocal() { |
|
495
|
|
|
return true; |
|
496
|
|
|
} |
|
497
|
|
|
|
|
498
|
|
|
/** |
|
499
|
|
|
* get the ETag for a file or folder |
|
500
|
|
|
* |
|
501
|
|
|
* @param string $path |
|
502
|
|
|
* @return string |
|
503
|
|
|
*/ |
|
504
|
|
|
public function getETag($path) { |
|
505
|
|
|
return $this->calculateEtag($path, $this->stat($path)); |
|
506
|
|
|
} |
|
507
|
|
|
|
|
508
|
|
|
private function calculateEtag(string $path, array $stat): string { |
|
509
|
|
|
if ($stat['mode'] & 0x4000 && !($stat['mode'] & 0x8000)) { // is_dir & not socket |
|
510
|
|
|
return parent::getETag($path); |
|
511
|
|
|
} else { |
|
512
|
|
|
if ($stat === false) { |
|
|
|
|
|
|
513
|
|
|
return md5(''); |
|
514
|
|
|
} |
|
515
|
|
|
|
|
516
|
|
|
$toHash = ''; |
|
517
|
|
|
if (isset($stat['mtime'])) { |
|
518
|
|
|
$toHash .= $stat['mtime']; |
|
519
|
|
|
} |
|
520
|
|
|
if (isset($stat['ino'])) { |
|
521
|
|
|
$toHash .= $stat['ino']; |
|
522
|
|
|
} |
|
523
|
|
|
if (isset($stat['dev'])) { |
|
524
|
|
|
$toHash .= $stat['dev']; |
|
525
|
|
|
} |
|
526
|
|
|
if (isset($stat['size'])) { |
|
527
|
|
|
$toHash .= $stat['size']; |
|
528
|
|
|
} |
|
529
|
|
|
|
|
530
|
|
|
return md5($toHash); |
|
531
|
|
|
} |
|
532
|
|
|
} |
|
533
|
|
|
|
|
534
|
|
|
/** |
|
535
|
|
|
* @param IStorage $sourceStorage |
|
536
|
|
|
* @param string $sourceInternalPath |
|
537
|
|
|
* @param string $targetInternalPath |
|
538
|
|
|
* @param bool $preserveMtime |
|
539
|
|
|
* @return bool |
|
540
|
|
|
*/ |
|
541
|
|
|
public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) { |
|
542
|
|
|
// Don't treat ACLStorageWrapper like local storage where copy can be done directly. |
|
543
|
|
|
// Instead use the slower recursive copying in php from Common::copyFromStorage with |
|
544
|
|
|
// more permissions checks. |
|
545
|
|
|
if ($sourceStorage->instanceOfStorage(Local::class) && !$sourceStorage->instanceOfStorage('OCA\GroupFolders\ACL\ACLStorageWrapper')) { |
|
546
|
|
|
if ($sourceStorage->instanceOfStorage(Jail::class)) { |
|
547
|
|
|
/** |
|
548
|
|
|
* @var \OC\Files\Storage\Wrapper\Jail $sourceStorage |
|
549
|
|
|
*/ |
|
550
|
|
|
$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath); |
|
551
|
|
|
} |
|
552
|
|
|
/** |
|
553
|
|
|
* @var \OC\Files\Storage\Local $sourceStorage |
|
554
|
|
|
*/ |
|
555
|
|
|
$rootStorage = new Local(['datadir' => '/']); |
|
556
|
|
|
return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath)); |
|
557
|
|
|
} else { |
|
558
|
|
|
return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); |
|
559
|
|
|
} |
|
560
|
|
|
} |
|
561
|
|
|
|
|
562
|
|
|
/** |
|
563
|
|
|
* @param IStorage $sourceStorage |
|
564
|
|
|
* @param string $sourceInternalPath |
|
565
|
|
|
* @param string $targetInternalPath |
|
566
|
|
|
* @return bool |
|
567
|
|
|
*/ |
|
568
|
|
|
public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { |
|
569
|
|
|
if ($sourceStorage->instanceOfStorage(Local::class)) { |
|
570
|
|
|
if ($sourceStorage->instanceOfStorage(Jail::class)) { |
|
571
|
|
|
/** |
|
572
|
|
|
* @var \OC\Files\Storage\Wrapper\Jail $sourceStorage |
|
573
|
|
|
*/ |
|
574
|
|
|
$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath); |
|
575
|
|
|
} |
|
576
|
|
|
/** |
|
577
|
|
|
* @var \OC\Files\Storage\Local $sourceStorage |
|
578
|
|
|
*/ |
|
579
|
|
|
$rootStorage = new Local(['datadir' => '/']); |
|
580
|
|
|
return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath)); |
|
581
|
|
|
} else { |
|
582
|
|
|
return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); |
|
583
|
|
|
} |
|
584
|
|
|
} |
|
585
|
|
|
|
|
586
|
|
|
public function writeStream(string $path, $stream, int $size = null): int { |
|
587
|
|
|
$result = $this->file_put_contents($path, $stream); |
|
588
|
|
|
if (is_resource($stream)) { |
|
589
|
|
|
fclose($stream); |
|
590
|
|
|
} |
|
591
|
|
|
if ($result === false) { |
|
592
|
|
|
throw new GenericFileException("Failed write stream to $path"); |
|
593
|
|
|
} else { |
|
594
|
|
|
return $result; |
|
595
|
|
|
} |
|
596
|
|
|
} |
|
597
|
|
|
} |
|
598
|
|
|
|
This class constant has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.