This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * @author Bart Visscher <[email protected]> |
||
4 | * @author Benjamin Liles <[email protected]> |
||
5 | * @author Christian Berendt <[email protected]> |
||
6 | * @author Daniel Tosello <[email protected]> |
||
7 | * @author Felix Moeller <[email protected]> |
||
8 | * @author Jörn Friedrich Dreyer <[email protected]> |
||
9 | * @author Martin Mattel <[email protected]> |
||
10 | * @author Morris Jobke <[email protected]> |
||
11 | * @author Philipp Kapfer <[email protected]> |
||
12 | * @author Robin Appelman <[email protected]> |
||
13 | * @author Robin McCorkell <[email protected]> |
||
14 | * @author Thomas Müller <[email protected]> |
||
15 | * @author Tim Dettrick <[email protected]> |
||
16 | * @author Vincent Petry <[email protected]> |
||
17 | * |
||
18 | * @copyright Copyright (c) 2018, ownCloud GmbH |
||
19 | * @license AGPL-3.0 |
||
20 | * |
||
21 | * This code is free software: you can redistribute it and/or modify |
||
22 | * it under the terms of the GNU Affero General Public License, version 3, |
||
23 | * as published by the Free Software Foundation. |
||
24 | * |
||
25 | * This program is distributed in the hope that it will be useful, |
||
26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
28 | * GNU Affero General Public License for more details. |
||
29 | * |
||
30 | * You should have received a copy of the GNU Affero General Public License, version 3, |
||
31 | * along with this program. If not, see <http://www.gnu.org/licenses/> |
||
32 | * |
||
33 | */ |
||
34 | |||
35 | namespace OCA\Files_External\Lib\Storage; |
||
36 | |||
37 | use Guzzle\Http\Exception\ClientErrorResponseException; |
||
38 | use Guzzle\Http\Url; |
||
39 | use Icewind\Streams\IteratorDirectory; |
||
40 | use OpenCloud; |
||
41 | use OpenCloud\Common\Exceptions; |
||
42 | use OpenCloud\ObjectStore\Resource\DataObject; |
||
43 | use OpenCloud\OpenStack; |
||
44 | use OpenCloud\Rackspace; |
||
45 | |||
46 | class Swift extends \OCP\Files\Storage\StorageAdapter { |
||
47 | |||
48 | /** |
||
49 | * @var \OpenCloud\ObjectStore\Service |
||
50 | */ |
||
51 | private $connection; |
||
52 | /** |
||
53 | * @var \OpenCloud\ObjectStore\Resource\Container |
||
54 | */ |
||
55 | private $container; |
||
56 | /** |
||
57 | * @var \OpenCloud\OpenStack |
||
58 | */ |
||
59 | private $anchor; |
||
60 | /** |
||
61 | * @var string |
||
62 | */ |
||
63 | private $bucket; |
||
64 | /** |
||
65 | * Connection parameters |
||
66 | * |
||
67 | * @var array |
||
68 | */ |
||
69 | private $params; |
||
70 | /** |
||
71 | * @var array |
||
72 | */ |
||
73 | private static $tmpFiles = []; |
||
74 | |||
75 | /** |
||
76 | * Key value cache mapping path to data object. Maps path to |
||
77 | * \OpenCloud\OpenStack\ObjectStorage\Resource\DataObject for existing |
||
78 | * paths and path to false for not existing paths. |
||
79 | * @var \OCP\ICache |
||
80 | */ |
||
81 | private $objectCache; |
||
82 | |||
83 | /** @var string */ |
||
84 | private $id; |
||
85 | |||
86 | /** |
||
87 | * @param string $path |
||
88 | */ |
||
89 | View Code Duplication | private function normalizePath($path) { |
|
90 | $path = \trim($path, '/'); |
||
91 | |||
92 | if (!$path) { |
||
93 | $path = '.'; |
||
94 | } |
||
95 | |||
96 | $path = \str_replace('#', '%23', $path); |
||
97 | |||
98 | return $path; |
||
99 | } |
||
100 | |||
101 | const SUBCONTAINER_FILE = '.subcontainers'; |
||
102 | |||
103 | /** |
||
104 | * translate directory path to container name |
||
105 | * |
||
106 | * @param string $path |
||
107 | * @return string |
||
108 | */ |
||
109 | |||
110 | /** |
||
111 | * Fetches an object from the API. |
||
112 | * If the object is cached already or a |
||
113 | * failed "doesn't exist" response was cached, |
||
114 | * that one will be returned. |
||
115 | * |
||
116 | * @param string $path |
||
117 | * @return DataObject | bool object |
||
118 | * or false if the object did not exist |
||
119 | */ |
||
120 | private function fetchObject($path) { |
||
121 | if ($this->objectCache->hasKey($path)) { |
||
122 | // might be "false" if object did not exist from last check |
||
123 | return $this->objectCache->get($path); |
||
124 | } |
||
125 | try { |
||
126 | $object = $this->getContainer()->getPartialObject($path); |
||
127 | $this->objectCache->set($path, $object); |
||
128 | return $object; |
||
129 | } catch (ClientErrorResponseException $e) { |
||
130 | // this exception happens when the object does not exist, which |
||
131 | // is expected in most cases |
||
132 | $this->objectCache->set($path, false); |
||
133 | return false; |
||
134 | } catch (ClientErrorResponseException $e) { |
||
135 | // Expected response is "404 Not Found", so only log if it isn't |
||
136 | if ($e->getResponse()->getStatusCode() !== 404) { |
||
0 ignored issues
–
show
Unused Code
Bug
introduced
by
Loading history...
|
|||
137 | \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
||
138 | } |
||
139 | return false; |
||
140 | } |
||
141 | } |
||
142 | |||
143 | /** |
||
144 | * Returns whether the given path exists. |
||
145 | * |
||
146 | * @param string $path |
||
147 | * |
||
148 | * @return bool true if the object exist, false otherwise |
||
149 | */ |
||
150 | private function doesObjectExist($path) { |
||
151 | return $this->fetchObject($path) !== false; |
||
152 | } |
||
153 | |||
154 | public function __construct($params) { |
||
155 | if ((empty($params['key']) and empty($params['password'])) |
||
156 | or empty($params['user']) or empty($params['bucket']) |
||
157 | or empty($params['region']) |
||
158 | ) { |
||
159 | throw new \Exception("API Key or password, Username, Bucket and Region have to be configured."); |
||
160 | } |
||
161 | |||
162 | $this->id = 'swift::' . $params['user'] . \md5($params['bucket']); |
||
163 | |||
164 | $bucketUrl = Url::factory($params['bucket']); |
||
165 | if ($bucketUrl->isAbsolute()) { |
||
166 | $this->bucket = \end(($bucketUrl->getPathSegments())); |
||
0 ignored issues
–
show
|
|||
167 | $params['endpoint_url'] = $bucketUrl->addPath('..')->normalizePath(); |
||
168 | } else { |
||
169 | $this->bucket = $params['bucket']; |
||
170 | } |
||
171 | |||
172 | if (empty($params['url'])) { |
||
173 | $params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/'; |
||
174 | } |
||
175 | |||
176 | if (empty($params['service_name'])) { |
||
177 | $params['service_name'] = 'cloudFiles'; |
||
178 | } |
||
179 | |||
180 | $this->params = $params; |
||
181 | // FIXME: private class... |
||
182 | $this->objectCache = new \OC\Cache\CappedMemoryCache(); |
||
183 | } |
||
184 | |||
185 | public function mkdir($path) { |
||
186 | $path = $this->normalizePath($path); |
||
187 | |||
188 | if ($this->is_dir($path)) { |
||
189 | return false; |
||
190 | } |
||
191 | |||
192 | if ($path !== '.') { |
||
193 | $path .= '/'; |
||
194 | } |
||
195 | |||
196 | try { |
||
197 | $customHeaders = ['content-type' => 'httpd/unix-directory']; |
||
198 | $metadataHeaders = DataObject::stockHeaders([]); |
||
199 | $allHeaders = $customHeaders + $metadataHeaders; |
||
200 | $this->getContainer()->uploadObject($path, '', $allHeaders); |
||
201 | // invalidate so that the next access gets the real object |
||
202 | // with all properties |
||
203 | $this->objectCache->remove($path); |
||
204 | } catch (Exceptions\CreateUpdateError $e) { |
||
205 | \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
||
206 | return false; |
||
207 | } |
||
208 | |||
209 | return true; |
||
210 | } |
||
211 | |||
212 | public function file_exists($path) { |
||
213 | $path = $this->normalizePath($path); |
||
214 | |||
215 | if ($path !== '.' && $this->is_dir($path)) { |
||
216 | $path .= '/'; |
||
217 | } |
||
218 | |||
219 | return $this->doesObjectExist($path); |
||
220 | } |
||
221 | |||
222 | public function rmdir($path) { |
||
223 | $path = $this->normalizePath($path); |
||
224 | |||
225 | if (!$this->is_dir($path) || !$this->isDeletable($path)) { |
||
226 | return false; |
||
227 | } |
||
228 | |||
229 | $dh = $this->opendir($path); |
||
230 | View Code Duplication | while ($file = \readdir($dh)) { |
|
231 | if (\OC\Files\Filesystem::isIgnoredDir($file)) { |
||
232 | continue; |
||
233 | } |
||
234 | |||
235 | if ($this->is_dir($path . '/' . $file)) { |
||
236 | $this->rmdir($path . '/' . $file); |
||
237 | } else { |
||
238 | $this->unlink($path . '/' . $file); |
||
239 | } |
||
240 | } |
||
241 | |||
242 | try { |
||
243 | $this->getContainer()->dataObject()->setName($path . '/')->delete(); |
||
244 | $this->objectCache->remove($path . '/'); |
||
245 | } catch (Exceptions\DeleteError $e) { |
||
246 | \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
||
247 | return false; |
||
248 | } |
||
249 | |||
250 | return true; |
||
251 | } |
||
252 | |||
253 | public function opendir($path) { |
||
254 | $path = $this->normalizePath($path); |
||
255 | |||
256 | if ($path === '.') { |
||
257 | $path = ''; |
||
258 | } else { |
||
259 | $path .= '/'; |
||
260 | } |
||
261 | |||
262 | $path = \str_replace('%23', '#', $path); // the prefix is sent as a query param, so revert the encoding of # |
||
263 | |||
264 | try { |
||
265 | $files = []; |
||
266 | /** @var OpenCloud\Common\Collection $objects */ |
||
267 | $objects = $this->getContainer()->objectList([ |
||
268 | 'prefix' => $path, |
||
269 | 'delimiter' => '/' |
||
270 | ]); |
||
271 | |||
272 | /** @var OpenCloud\ObjectStore\Resource\DataObject $object */ |
||
273 | foreach ($objects as $object) { |
||
274 | $file = \basename($object->getName()); |
||
275 | if ($file !== \basename($path)) { |
||
276 | $files[] = $file; |
||
277 | } |
||
278 | } |
||
279 | |||
280 | return IteratorDirectory::wrap($files); |
||
281 | } catch (\Exception $e) { |
||
282 | \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
||
283 | return false; |
||
284 | } |
||
285 | } |
||
286 | |||
287 | public function stat($path) { |
||
288 | $path = $this->normalizePath($path); |
||
289 | |||
290 | if ($path === '.') { |
||
291 | $path = ''; |
||
292 | } elseif ($this->is_dir($path)) { |
||
293 | $path .= '/'; |
||
294 | } |
||
295 | |||
296 | try { |
||
297 | /** @var DataObject $object */ |
||
298 | $object = $this->fetchObject($path); |
||
299 | if (!$object) { |
||
300 | return false; |
||
301 | } |
||
302 | } catch (ClientErrorResponseException $e) { |
||
303 | \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
||
304 | return false; |
||
305 | } |
||
306 | |||
307 | $dateTime = \DateTime::createFromFormat(\DateTime::RFC1123, $object->getLastModified()); |
||
308 | if ($dateTime !== false) { |
||
309 | $mtime = $dateTime->getTimestamp(); |
||
310 | } else { |
||
311 | $mtime = null; |
||
312 | } |
||
313 | $objectMetadata = $object->getMetadata(); |
||
314 | $metaTimestamp = $objectMetadata->getProperty('timestamp'); |
||
315 | if (isset($metaTimestamp)) { |
||
316 | $mtime = $metaTimestamp; |
||
317 | } |
||
318 | |||
319 | if (!empty($mtime)) { |
||
320 | $mtime = \floor($mtime); |
||
321 | } |
||
322 | |||
323 | $stat = []; |
||
324 | $stat['size'] = (int)$object->getContentLength(); |
||
325 | $stat['mtime'] = $mtime; |
||
326 | $stat['atime'] = \time(); |
||
327 | return $stat; |
||
328 | } |
||
329 | |||
330 | public function filetype($path) { |
||
331 | $path = $this->normalizePath($path); |
||
332 | |||
333 | if ($path !== '.' && $this->doesObjectExist($path)) { |
||
334 | return 'file'; |
||
335 | } |
||
336 | |||
337 | if ($path !== '.') { |
||
338 | $path .= '/'; |
||
339 | } |
||
340 | |||
341 | if ($this->doesObjectExist($path)) { |
||
342 | return 'dir'; |
||
343 | } |
||
344 | } |
||
345 | |||
346 | public function unlink($path) { |
||
347 | $path = $this->normalizePath($path); |
||
348 | |||
349 | if ($this->is_dir($path)) { |
||
350 | return $this->rmdir($path); |
||
351 | } |
||
352 | |||
353 | try { |
||
354 | $this->getContainer()->dataObject()->setName($path)->delete(); |
||
355 | $this->objectCache->remove($path); |
||
356 | $this->objectCache->remove($path . '/'); |
||
357 | } catch (ClientErrorResponseException $e) { |
||
358 | if ($e->getResponse()->getStatusCode() !== 404) { |
||
359 | \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
||
360 | } |
||
361 | return false; |
||
362 | } |
||
363 | |||
364 | return true; |
||
365 | } |
||
366 | |||
367 | public function fopen($path, $mode) { |
||
368 | $path = $this->normalizePath($path); |
||
369 | |||
370 | switch ($mode) { |
||
371 | case 'r': |
||
372 | case 'rb': |
||
373 | try { |
||
374 | $c = $this->getContainer(); |
||
375 | $streamFactory = new \Guzzle\Stream\PhpStreamRequestFactory(); |
||
376 | $streamInterface = $streamFactory->fromRequest( |
||
377 | $c->getClient() |
||
378 | ->get($c->getUrl($path))); |
||
379 | $streamInterface->rewind(); |
||
380 | $stream = $streamInterface->getStream(); |
||
381 | \stream_context_set_option($stream, 'swift', 'content', $streamInterface); |
||
382 | if (!\strrpos($streamInterface |
||
383 | ->getMetaData('wrapper_data')[0], '404 Not Found')) { |
||
384 | return $stream; |
||
385 | } |
||
386 | return false; |
||
387 | } catch (\Guzzle\Http\Exception\BadResponseException $e) { |
||
388 | \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
||
389 | return false; |
||
390 | } |
||
391 | case 'w': |
||
392 | case 'wb': |
||
393 | case 'a': |
||
394 | case 'ab': |
||
395 | case 'r+': |
||
396 | case 'w+': |
||
397 | case 'wb+': |
||
398 | case 'a+': |
||
399 | case 'x': |
||
400 | case 'x+': |
||
401 | case 'c': |
||
402 | case 'c+': |
||
403 | if (\strrpos($path, '.') !== false) { |
||
404 | $ext = \substr($path, \strrpos($path, '.')); |
||
405 | } else { |
||
406 | $ext = ''; |
||
407 | } |
||
408 | $tmpFile = \OCP\Files::tmpFile($ext); |
||
409 | \OC\Files\Stream\Close::registerCallback($tmpFile, [$this, 'writeBack']); |
||
410 | // Fetch existing file if required |
||
411 | if ($mode[0] !== 'w' && $this->file_exists($path)) { |
||
412 | if ($mode[0] === 'x') { |
||
413 | // File cannot already exist |
||
414 | return false; |
||
415 | } |
||
416 | $source = $this->fopen($path, 'r'); |
||
417 | \file_put_contents($tmpFile, $source); |
||
418 | // Seek to end if required |
||
419 | if ($mode[0] === 'a') { |
||
420 | \fseek($tmpFile, 0, SEEK_END); |
||
421 | } |
||
422 | } |
||
423 | self::$tmpFiles[$tmpFile] = $path; |
||
424 | |||
425 | return \fopen('close://' . $tmpFile, $mode); |
||
426 | } |
||
427 | } |
||
428 | |||
429 | public function touch($path, $mtime = null) { |
||
430 | $path = $this->normalizePath($path); |
||
431 | if ($mtime === null) { |
||
432 | $mtime = \time(); |
||
433 | } |
||
434 | $metadata = ['timestamp' => $mtime]; |
||
435 | if ($this->file_exists($path)) { |
||
436 | if ($this->is_dir($path) && $path != '.') { |
||
437 | $path .= '/'; |
||
438 | } |
||
439 | |||
440 | $object = $this->fetchObject($path); |
||
441 | if ($object->saveMetadata($metadata)) { |
||
442 | // invalidate target object to force repopulation on fetch |
||
443 | $this->objectCache->remove($path); |
||
444 | } |
||
445 | return true; |
||
446 | } else { |
||
447 | $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); |
||
448 | $customHeaders = ['content-type' => $mimeType]; |
||
449 | $metadataHeaders = DataObject::stockHeaders($metadata); |
||
450 | $allHeaders = $customHeaders + $metadataHeaders; |
||
451 | $this->getContainer()->uploadObject($path, '', $allHeaders); |
||
452 | // invalidate target object to force repopulation on fetch |
||
453 | $this->objectCache->remove($path); |
||
454 | return true; |
||
455 | } |
||
456 | } |
||
457 | |||
458 | public function copy($path1, $path2) { |
||
459 | $path1 = $this->normalizePath($path1); |
||
460 | $path2 = $this->normalizePath($path2); |
||
461 | |||
462 | $fileType = $this->filetype($path1); |
||
463 | if ($fileType === 'file') { |
||
464 | |||
465 | // make way |
||
466 | $this->unlink($path2); |
||
467 | |||
468 | try { |
||
469 | $source = $this->fetchObject($path1); |
||
470 | $source->copy($this->bucket . '/' . $path2); |
||
471 | // invalidate target object to force repopulation on fetch |
||
472 | $this->objectCache->remove($path2); |
||
473 | $this->objectCache->remove($path2 . '/'); |
||
474 | } catch (ClientErrorResponseException $e) { |
||
475 | \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
||
476 | return false; |
||
477 | } |
||
478 | } elseif ($fileType === 'dir') { |
||
479 | |||
480 | // make way |
||
481 | $this->unlink($path2); |
||
482 | |||
483 | try { |
||
484 | $source = $this->fetchObject($path1 . '/'); |
||
485 | $source->copy($this->bucket . '/' . $path2 . '/'); |
||
486 | // invalidate target object to force repopulation on fetch |
||
487 | $this->objectCache->remove($path2); |
||
488 | $this->objectCache->remove($path2 . '/'); |
||
489 | } catch (ClientErrorResponseException $e) { |
||
490 | \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); |
||
491 | return false; |
||
492 | } |
||
493 | |||
494 | $dh = $this->opendir($path1); |
||
495 | while ($file = \readdir($dh)) { |
||
496 | if (\OC\Files\Filesystem::isIgnoredDir($file)) { |
||
497 | continue; |
||
498 | } |
||
499 | |||
500 | $source = $path1 . '/' . $file; |
||
501 | $target = $path2 . '/' . $file; |
||
502 | $this->copy($source, $target); |
||
503 | } |
||
504 | } else { |
||
505 | //file does not exist |
||
506 | return false; |
||
507 | } |
||
508 | |||
509 | return true; |
||
510 | } |
||
511 | |||
512 | public function rename($path1, $path2) { |
||
513 | $path1 = $this->normalizePath($path1); |
||
514 | $path2 = $this->normalizePath($path2); |
||
515 | |||
516 | $fileType = $this->filetype($path1); |
||
517 | |||
518 | if ($fileType === 'dir' || $fileType === 'file') { |
||
519 | // copy |
||
520 | if ($this->copy($path1, $path2) === false) { |
||
521 | return false; |
||
522 | } |
||
523 | |||
524 | // cleanup |
||
525 | if ($this->unlink($path1) === false) { |
||
526 | $this->unlink($path2); |
||
527 | return false; |
||
528 | } |
||
529 | |||
530 | return true; |
||
531 | } |
||
532 | |||
533 | return false; |
||
534 | } |
||
535 | |||
536 | public function getId() { |
||
537 | return $this->id; |
||
538 | } |
||
539 | |||
540 | /** |
||
541 | * Returns the connection |
||
542 | * |
||
543 | * @return OpenCloud\ObjectStore\Service connected client |
||
544 | * @throws \Exception if connection could not be made |
||
545 | */ |
||
546 | public function getConnection() { |
||
547 | if ($this->connection !== null) { |
||
548 | return $this->connection; |
||
549 | } |
||
550 | |||
551 | $settings = [ |
||
552 | 'username' => $this->params['user'], |
||
553 | ]; |
||
554 | |||
555 | if (!empty($this->params['password'])) { |
||
556 | $settings['password'] = $this->params['password']; |
||
557 | } elseif (!empty($this->params['key'])) { |
||
558 | $settings['apiKey'] = $this->params['key']; |
||
559 | } |
||
560 | |||
561 | if (!empty($this->params['tenant'])) { |
||
562 | $settings['tenantName'] = $this->params['tenant']; |
||
563 | } |
||
564 | |||
565 | if (!empty($this->params['timeout'])) { |
||
566 | $settings['timeout'] = $this->params['timeout']; |
||
567 | } |
||
568 | |||
569 | if (isset($settings['apiKey'])) { |
||
570 | $this->anchor = new Rackspace($this->params['url'], $settings); |
||
571 | } else { |
||
572 | $this->anchor = new OpenStack($this->params['url'], $settings); |
||
573 | } |
||
574 | |||
575 | $connection = $this->anchor->objectStoreService($this->params['service_name'], $this->params['region']); |
||
576 | |||
577 | if (!empty($this->params['endpoint_url'])) { |
||
578 | $endpoint = $connection->getEndpoint(); |
||
579 | $endpoint->setPublicUrl($this->params['endpoint_url']); |
||
580 | $endpoint->setPrivateUrl($this->params['endpoint_url']); |
||
581 | $connection->setEndpoint($endpoint); |
||
582 | } |
||
583 | |||
584 | $this->connection = $connection; |
||
585 | |||
586 | return $this->connection; |
||
587 | } |
||
588 | |||
589 | /** |
||
590 | * Returns the initialized object store container. |
||
591 | * |
||
592 | * @return OpenCloud\ObjectStore\Resource\Container |
||
593 | */ |
||
594 | public function getContainer() { |
||
595 | if ($this->container !== null) { |
||
596 | return $this->container; |
||
597 | } |
||
598 | |||
599 | try { |
||
600 | $this->container = $this->getConnection()->getContainer($this->bucket); |
||
601 | } catch (ClientErrorResponseException $e) { |
||
602 | $this->container = $this->getConnection()->createContainer($this->bucket); |
||
603 | } |
||
604 | |||
605 | if (!$this->file_exists('.')) { |
||
606 | $this->mkdir('.'); |
||
607 | } |
||
608 | |||
609 | return $this->container; |
||
610 | } |
||
611 | |||
612 | public function writeBack($tmpFile) { |
||
613 | if (!isset(self::$tmpFiles[$tmpFile])) { |
||
614 | return false; |
||
615 | } |
||
616 | $fileData = \fopen($tmpFile, 'r'); |
||
617 | $this->getContainer()->uploadObject(self::$tmpFiles[$tmpFile], $fileData); |
||
618 | // invalidate target object to force repopulation on fetch |
||
619 | $this->objectCache->remove(self::$tmpFiles[$tmpFile]); |
||
620 | \unlink($tmpFile); |
||
621 | } |
||
622 | |||
623 | public function hasUpdated($path, $time) { |
||
624 | if ($this->is_file($path)) { |
||
625 | return parent::hasUpdated($path, $time); |
||
626 | } |
||
627 | $path = $this->normalizePath($path); |
||
628 | $dh = $this->opendir($path); |
||
629 | $content = []; |
||
630 | while (($file = \readdir($dh)) !== false) { |
||
631 | $content[] = $file; |
||
632 | } |
||
633 | if ($path === '.') { |
||
634 | $path = ''; |
||
635 | } |
||
636 | $cachedContent = $this->getCache()->getFolderContents($path); |
||
637 | $cachedNames = \array_map(function ($content) { |
||
638 | return $content['name']; |
||
639 | }, $cachedContent); |
||
640 | \sort($cachedNames); |
||
641 | \sort($content); |
||
642 | return $cachedNames != $content; |
||
643 | } |
||
644 | |||
645 | /** |
||
646 | * check if curl is installed |
||
647 | */ |
||
648 | public static function checkDependencies() { |
||
649 | return true; |
||
650 | } |
||
651 | } |
||
652 |