Issues (1798)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

apps/files_external/lib/Lib/Storage/Swift.php (2 issues)

Upgrade to new PHP Analysis Engine

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
The strict comparison !== seems to always evaluate to true as the types of $e->getResponse()->getStatusCode() (string) and 404 (integer) can never be identical. Maybe you want to use a loose comparison != instead?
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
$bucketUrl->getPathSegments() cannot be passed to end() as the parameter $array expects a reference.
Loading history...
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