Passed
Push — master ( d23e96...1bc100 )
by Roeland
11:25 queued 10s
created
lib/private/Files/ObjectStore/NoopScanner.php 1 patch
Indentation   +44 added lines, -44 removed lines patch added patch discarded remove patch
@@ -29,52 +29,52 @@
 block discarded – undo
29 29
 
30 30
 class NoopScanner extends Scanner {
31 31
 
32
-	public function __construct(Storage $storage) {
33
-		//we don't need the storage, so do nothing here
34
-	}
32
+    public function __construct(Storage $storage) {
33
+        //we don't need the storage, so do nothing here
34
+    }
35 35
 
36
-	/**
37
-	 * scan a single file and store it in the cache
38
-	 *
39
-	 * @param string $file
40
-	 * @param int $reuseExisting
41
-	 * @param int $parentId
42
-	 * @param array|null $cacheData existing data in the cache for the file to be scanned
43
-	 * @return array an array of metadata of the scanned file
44
-	 */
45
-	public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true) {
46
-		return array();
47
-	}
36
+    /**
37
+     * scan a single file and store it in the cache
38
+     *
39
+     * @param string $file
40
+     * @param int $reuseExisting
41
+     * @param int $parentId
42
+     * @param array|null $cacheData existing data in the cache for the file to be scanned
43
+     * @return array an array of metadata of the scanned file
44
+     */
45
+    public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true) {
46
+        return array();
47
+    }
48 48
 
49
-	/**
50
-	 * scan a folder and all it's children
51
-	 *
52
-	 * @param string $path
53
-	 * @param bool $recursive
54
-	 * @param int $reuse
55
-	 * @return array with the meta data of the scanned file or folder
56
-	 */
57
-	public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
58
-		return array();
59
-	}
49
+    /**
50
+     * scan a folder and all it's children
51
+     *
52
+     * @param string $path
53
+     * @param bool $recursive
54
+     * @param int $reuse
55
+     * @return array with the meta data of the scanned file or folder
56
+     */
57
+    public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
58
+        return array();
59
+    }
60 60
 
61
-	/**
62
-	 * scan all the files and folders in a folder
63
-	 *
64
-	 * @param string $path
65
-	 * @param bool $recursive
66
-	 * @param int $reuse
67
-	 * @param array $folderData existing cache data for the folder to be scanned
68
-	 * @return int the size of the scanned folder or -1 if the size is unknown at this stage
69
-	 */
70
-	protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderData = null, $lock = true) {
71
-		return 0;
72
-	}
61
+    /**
62
+     * scan all the files and folders in a folder
63
+     *
64
+     * @param string $path
65
+     * @param bool $recursive
66
+     * @param int $reuse
67
+     * @param array $folderData existing cache data for the folder to be scanned
68
+     * @return int the size of the scanned folder or -1 if the size is unknown at this stage
69
+     */
70
+    protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderData = null, $lock = true) {
71
+        return 0;
72
+    }
73 73
 
74
-	/**
75
-	 * walk over any folders that are not fully scanned yet and scan them
76
-	 */
77
-	public function backgroundScan() {
78
-		//noop
79
-	}
74
+    /**
75
+     * walk over any folders that are not fully scanned yet and scan them
76
+     */
77
+    public function backgroundScan() {
78
+        //noop
79
+    }
80 80
 }
Please login to merge, or discard this patch.
lib/private/Files/ObjectStore/Mapper.php 2 patches
Indentation   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -31,25 +31,25 @@
 block discarded – undo
31 31
  * Map a user to a bucket.
32 32
  */
33 33
 class Mapper {
34
-	/** @var IUser */
35
-	private $user;
34
+    /** @var IUser */
35
+    private $user;
36 36
 
37
-	/**
38
-	 * Mapper constructor.
39
-	 *
40
-	 * @param IUser $user
41
-	 */
42
-	public function __construct(IUser $user) {
43
-		$this->user = $user;
44
-	}
37
+    /**
38
+     * Mapper constructor.
39
+     *
40
+     * @param IUser $user
41
+     */
42
+    public function __construct(IUser $user) {
43
+        $this->user = $user;
44
+    }
45 45
 
46
-	/**
47
-	 * @param int $numBuckets
48
-	 * @return string
49
-	 */
50
-	public function getBucket($numBuckets = 64) {
51
-		$hash = md5($this->user->getUID());
52
-		$num = hexdec(substr($hash, 0, 4));
53
-		return (string)($num % $numBuckets);
54
-	}
46
+    /**
47
+     * @param int $numBuckets
48
+     * @return string
49
+     */
50
+    public function getBucket($numBuckets = 64) {
51
+        $hash = md5($this->user->getUID());
52
+        $num = hexdec(substr($hash, 0, 4));
53
+        return (string)($num % $numBuckets);
54
+    }
55 55
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -50,6 +50,6 @@
 block discarded – undo
50 50
 	public function getBucket($numBuckets = 64) {
51 51
 		$hash = md5($this->user->getUID());
52 52
 		$num = hexdec(substr($hash, 0, 4));
53
-		return (string)($num % $numBuckets);
53
+		return (string) ($num % $numBuckets);
54 54
 	}
55 55
 }
Please login to merge, or discard this patch.
lib/private/Files/Type/TemplateManager.php 1 patch
Indentation   +31 added lines, -31 removed lines patch added patch discarded remove patch
@@ -25,38 +25,38 @@
 block discarded – undo
25 25
 namespace OC\Files\Type;
26 26
 
27 27
 class TemplateManager {
28
-	protected $templates = array();
28
+    protected $templates = array();
29 29
 
30
-	public function registerTemplate($mimetype, $path) {
31
-		$this->templates[$mimetype] = $path;
32
-	}
30
+    public function registerTemplate($mimetype, $path) {
31
+        $this->templates[$mimetype] = $path;
32
+    }
33 33
 
34
-	/**
35
-	 * get the path of the template for a mimetype
36
-	 *
37
-	 * @param string $mimetype
38
-	 * @return string|null
39
-	 */
40
-	public function getTemplatePath($mimetype) {
41
-		if (isset($this->templates[$mimetype])) {
42
-			return $this->templates[$mimetype];
43
-		} else {
44
-			return null;
45
-		}
46
-	}
34
+    /**
35
+     * get the path of the template for a mimetype
36
+     *
37
+     * @param string $mimetype
38
+     * @return string|null
39
+     */
40
+    public function getTemplatePath($mimetype) {
41
+        if (isset($this->templates[$mimetype])) {
42
+            return $this->templates[$mimetype];
43
+        } else {
44
+            return null;
45
+        }
46
+    }
47 47
 
48
-	/**
49
-	 * get the template content for a mimetype
50
-	 *
51
-	 * @param string $mimetype
52
-	 * @return string
53
-	 */
54
-	public function getTemplate($mimetype) {
55
-		$path = $this->getTemplatePath($mimetype);
56
-		if ($path) {
57
-			return file_get_contents($path);
58
-		} else {
59
-			return '';
60
-		}
61
-	}
48
+    /**
49
+     * get the template content for a mimetype
50
+     *
51
+     * @param string $mimetype
52
+     * @return string
53
+     */
54
+    public function getTemplate($mimetype) {
55
+        $path = $this->getTemplatePath($mimetype);
56
+        if ($path) {
57
+            return file_get_contents($path);
58
+        } else {
59
+            return '';
60
+        }
61
+    }
62 62
 }
Please login to merge, or discard this patch.
lib/private/Files/SimpleFS/SimpleFile.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -26,7 +26,7 @@
 block discarded – undo
26 26
 use OCP\Files\NotPermittedException;
27 27
 use OCP\Files\SimpleFS\ISimpleFile;
28 28
 
29
-class SimpleFile implements ISimpleFile  {
29
+class SimpleFile implements ISimpleFile {
30 30
 
31 31
 	/** @var File $file */
32 32
 	private $file;
Please login to merge, or discard this patch.
Indentation   +148 added lines, -148 removed lines patch added patch discarded remove patch
@@ -29,153 +29,153 @@
 block discarded – undo
29 29
 
30 30
 class SimpleFile implements ISimpleFile  {
31 31
 
32
-	/** @var File $file */
33
-	private $file;
34
-
35
-	/**
36
-	 * File constructor.
37
-	 *
38
-	 * @param File $file
39
-	 */
40
-	public function __construct(File $file) {
41
-		$this->file = $file;
42
-	}
43
-
44
-	/**
45
-	 * Get the name
46
-	 *
47
-	 * @return string
48
-	 */
49
-	public function getName() {
50
-		return $this->file->getName();
51
-	}
52
-
53
-	/**
54
-	 * Get the size in bytes
55
-	 *
56
-	 * @return int
57
-	 */
58
-	public function getSize() {
59
-		return $this->file->getSize();
60
-	}
61
-
62
-	/**
63
-	 * Get the ETag
64
-	 *
65
-	 * @return string
66
-	 */
67
-	public function getETag() {
68
-		return $this->file->getEtag();
69
-	}
70
-
71
-	/**
72
-	 * Get the last modification time
73
-	 *
74
-	 * @return int
75
-	 */
76
-	public function getMTime() {
77
-		return $this->file->getMTime();
78
-	}
79
-
80
-	/**
81
-	 * Get the content
82
-	 *
83
-	 * @throws NotPermittedException
84
-	 * @throws NotFoundException
85
-	 * @return string
86
-	 */
87
-	public function getContent() {
88
-		$result = $this->file->getContent();
89
-
90
-		if ($result === false) {
91
-			$this->checkFile();
92
-		}
93
-
94
-		return $result;
95
-	}
96
-
97
-	/**
98
-	 * Overwrite the file
99
-	 *
100
-	 * @param string|resource $data
101
-	 * @throws NotPermittedException
102
-	 * @throws NotFoundException
103
-	 */
104
-	public function putContent($data) {
105
-		try {
106
-			return $this->file->putContent($data);
107
-		} catch (NotFoundException $e) {
108
-			$this->checkFile();
109
-		}
110
-	}
111
-
112
-	/**
113
-	 * Sometimes there are some issues with the AppData. Most of them are from
114
-	 * user error. But we should handle them gracefull anyway.
115
-	 *
116
-	 * If for some reason the current file can't be found. We remove it.
117
-	 * Then traverse up and check all folders if they exists. This so that the
118
-	 * next request will have a valid appdata structure again.
119
-	 *
120
-	 * @throws NotFoundException
121
-	 */
122
-	private function checkFile() {
123
-		$cur = $this->file;
124
-
125
-		while ($cur->stat() === false) {
126
-			$parent = $cur->getParent();
127
-			try {
128
-				$cur->delete();
129
-			} catch (NotFoundException $e) {
130
-				// Just continue then
131
-			}
132
-			$cur = $parent;
133
-		}
134
-
135
-		if ($cur !== $this->file) {
136
-			throw new NotFoundException('File does not exist');
137
-		}
138
-	}
139
-
140
-
141
-	/**
142
-	 * Delete the file
143
-	 *
144
-	 * @throws NotPermittedException
145
-	 */
146
-	public function delete() {
147
-		$this->file->delete();
148
-	}
149
-
150
-	/**
151
-	 * Get the MimeType
152
-	 *
153
-	 * @return string
154
-	 */
155
-	public function getMimeType() {
156
-		return $this->file->getMimeType();
157
-	}
158
-
159
-	/**
160
-	 * Open the file as stream for reading, resulting resource can be operated as stream like the result from php's own fopen
161
-	 *
162
-	 * @return resource
163
-	 * @throws \OCP\Files\NotPermittedException
164
-	 * @since 14.0.0
165
-	 */
166
-	public function read() {
167
-		return $this->file->fopen('r');
168
-	}
169
-
170
-	/**
171
-	 * Open the file as stream for writing, resulting resource can be operated as stream like the result from php's own fopen
172
-	 *
173
-	 * @return resource
174
-	 * @throws \OCP\Files\NotPermittedException
175
-	 * @since 14.0.0
176
-	 */
177
-	public function write() {
178
-		return $this->file->fopen('w');
179
-	}
32
+    /** @var File $file */
33
+    private $file;
34
+
35
+    /**
36
+     * File constructor.
37
+     *
38
+     * @param File $file
39
+     */
40
+    public function __construct(File $file) {
41
+        $this->file = $file;
42
+    }
43
+
44
+    /**
45
+     * Get the name
46
+     *
47
+     * @return string
48
+     */
49
+    public function getName() {
50
+        return $this->file->getName();
51
+    }
52
+
53
+    /**
54
+     * Get the size in bytes
55
+     *
56
+     * @return int
57
+     */
58
+    public function getSize() {
59
+        return $this->file->getSize();
60
+    }
61
+
62
+    /**
63
+     * Get the ETag
64
+     *
65
+     * @return string
66
+     */
67
+    public function getETag() {
68
+        return $this->file->getEtag();
69
+    }
70
+
71
+    /**
72
+     * Get the last modification time
73
+     *
74
+     * @return int
75
+     */
76
+    public function getMTime() {
77
+        return $this->file->getMTime();
78
+    }
79
+
80
+    /**
81
+     * Get the content
82
+     *
83
+     * @throws NotPermittedException
84
+     * @throws NotFoundException
85
+     * @return string
86
+     */
87
+    public function getContent() {
88
+        $result = $this->file->getContent();
89
+
90
+        if ($result === false) {
91
+            $this->checkFile();
92
+        }
93
+
94
+        return $result;
95
+    }
96
+
97
+    /**
98
+     * Overwrite the file
99
+     *
100
+     * @param string|resource $data
101
+     * @throws NotPermittedException
102
+     * @throws NotFoundException
103
+     */
104
+    public function putContent($data) {
105
+        try {
106
+            return $this->file->putContent($data);
107
+        } catch (NotFoundException $e) {
108
+            $this->checkFile();
109
+        }
110
+    }
111
+
112
+    /**
113
+     * Sometimes there are some issues with the AppData. Most of them are from
114
+     * user error. But we should handle them gracefull anyway.
115
+     *
116
+     * If for some reason the current file can't be found. We remove it.
117
+     * Then traverse up and check all folders if they exists. This so that the
118
+     * next request will have a valid appdata structure again.
119
+     *
120
+     * @throws NotFoundException
121
+     */
122
+    private function checkFile() {
123
+        $cur = $this->file;
124
+
125
+        while ($cur->stat() === false) {
126
+            $parent = $cur->getParent();
127
+            try {
128
+                $cur->delete();
129
+            } catch (NotFoundException $e) {
130
+                // Just continue then
131
+            }
132
+            $cur = $parent;
133
+        }
134
+
135
+        if ($cur !== $this->file) {
136
+            throw new NotFoundException('File does not exist');
137
+        }
138
+    }
139
+
140
+
141
+    /**
142
+     * Delete the file
143
+     *
144
+     * @throws NotPermittedException
145
+     */
146
+    public function delete() {
147
+        $this->file->delete();
148
+    }
149
+
150
+    /**
151
+     * Get the MimeType
152
+     *
153
+     * @return string
154
+     */
155
+    public function getMimeType() {
156
+        return $this->file->getMimeType();
157
+    }
158
+
159
+    /**
160
+     * Open the file as stream for reading, resulting resource can be operated as stream like the result from php's own fopen
161
+     *
162
+     * @return resource
163
+     * @throws \OCP\Files\NotPermittedException
164
+     * @since 14.0.0
165
+     */
166
+    public function read() {
167
+        return $this->file->fopen('r');
168
+    }
169
+
170
+    /**
171
+     * Open the file as stream for writing, resulting resource can be operated as stream like the result from php's own fopen
172
+     *
173
+     * @return resource
174
+     * @throws \OCP\Files\NotPermittedException
175
+     * @since 14.0.0
176
+     */
177
+    public function write() {
178
+        return $this->file->fopen('w');
179
+    }
180 180
 
181 181
 }
Please login to merge, or discard this patch.
lib/private/Files/SimpleFS/SimpleFolder.php 2 patches
Indentation   +41 added lines, -41 removed lines patch added patch discarded remove patch
@@ -30,58 +30,58 @@
 block discarded – undo
30 30
 
31 31
 class SimpleFolder implements ISimpleFolder   {
32 32
 
33
-	/** @var Folder */
34
-	private $folder;
33
+    /** @var Folder */
34
+    private $folder;
35 35
 
36
-	/**
37
-	 * Folder constructor.
38
-	 *
39
-	 * @param Folder $folder
40
-	 */
41
-	public function __construct(Folder $folder) {
42
-		$this->folder = $folder;
43
-	}
36
+    /**
37
+     * Folder constructor.
38
+     *
39
+     * @param Folder $folder
40
+     */
41
+    public function __construct(Folder $folder) {
42
+        $this->folder = $folder;
43
+    }
44 44
 
45
-	public function getName() {
46
-		return $this->folder->getName();
47
-	}
45
+    public function getName() {
46
+        return $this->folder->getName();
47
+    }
48 48
 
49
-	public function getDirectoryListing() {
50
-		$listing = $this->folder->getDirectoryListing();
49
+    public function getDirectoryListing() {
50
+        $listing = $this->folder->getDirectoryListing();
51 51
 
52
-		$fileListing = array_map(function(Node $file) {
53
-			if ($file instanceof File) {
54
-				return new SimpleFile($file);
55
-			}
56
-			return null;
57
-		}, $listing);
52
+        $fileListing = array_map(function(Node $file) {
53
+            if ($file instanceof File) {
54
+                return new SimpleFile($file);
55
+            }
56
+            return null;
57
+        }, $listing);
58 58
 
59
-		$fileListing = array_filter($fileListing);
59
+        $fileListing = array_filter($fileListing);
60 60
 
61
-		return array_values($fileListing);
62
-	}
61
+        return array_values($fileListing);
62
+    }
63 63
 
64
-	public function delete() {
65
-		$this->folder->delete();
66
-	}
64
+    public function delete() {
65
+        $this->folder->delete();
66
+    }
67 67
 
68
-	public function fileExists($name) {
69
-		return $this->folder->nodeExists($name);
70
-	}
68
+    public function fileExists($name) {
69
+        return $this->folder->nodeExists($name);
70
+    }
71 71
 
72
-	public function getFile($name) {
73
-		$file = $this->folder->get($name);
72
+    public function getFile($name) {
73
+        $file = $this->folder->get($name);
74 74
 
75
-		if (!($file instanceof File)) {
76
-			throw new NotFoundException();
77
-		}
75
+        if (!($file instanceof File)) {
76
+            throw new NotFoundException();
77
+        }
78 78
 
79
-		return new SimpleFile($file);
80
-	}
79
+        return new SimpleFile($file);
80
+    }
81 81
 
82
-	public function newFile($name) {
83
-		$file = $this->folder->newFile($name);
82
+    public function newFile($name) {
83
+        $file = $this->folder->newFile($name);
84 84
 
85
-		return new SimpleFile($file);
86
-	}
85
+        return new SimpleFile($file);
86
+    }
87 87
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -28,7 +28,7 @@
 block discarded – undo
28 28
 use OCP\Files\NotFoundException;
29 29
 use OCP\Files\SimpleFS\ISimpleFolder;
30 30
 
31
-class SimpleFolder implements ISimpleFolder   {
31
+class SimpleFolder implements ISimpleFolder {
32 32
 
33 33
 	/** @var Folder */
34 34
 	private $folder;
Please login to merge, or discard this patch.
lib/private/Files/Storage/Wrapper/Encoding.php 2 patches
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -80,7 +80,7 @@  discard block
 block discarded – undo
80 80
 				// no point in continuing if the section was not found, use original path
81 81
 				return $fullPath;
82 82
 			}
83
-			$path = $convertedPath . '/';
83
+			$path = $convertedPath.'/';
84 84
 		}
85 85
 		$path = rtrim($path, '/');
86 86
 		return $path;
@@ -96,7 +96,7 @@  discard block
 block discarded – undo
96 96
 	 * @return string|null original or converted path, or null if none of the forms was found
97 97
 	 */
98 98
 	private function findPathToUseLastSection($basePath, $lastSection) {
99
-		$fullPath = $basePath . $lastSection;
99
+		$fullPath = $basePath.$lastSection;
100 100
 		if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
101 101
 			$this->namesCache[$fullPath] = $fullPath;
102 102
 			return $fullPath;
@@ -108,7 +108,7 @@  discard block
 block discarded – undo
108 108
 		} else {
109 109
 			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
110 110
 		}
111
-		$otherFullPath = $basePath . $otherFormPath;
111
+		$otherFullPath = $basePath.$otherFormPath;
112 112
 		if ($this->storage->file_exists($otherFullPath)) {
113 113
 			$this->namesCache[$fullPath] = $otherFullPath;
114 114
 			return $otherFullPath;
Please login to merge, or discard this patch.
Indentation   +498 added lines, -498 removed lines patch added patch discarded remove patch
@@ -34,502 +34,502 @@
 block discarded – undo
34 34
  */
35 35
 class Encoding extends Wrapper {
36 36
 
37
-	/**
38
-	 * @var ICache
39
-	 */
40
-	private $namesCache;
41
-
42
-	/**
43
-	 * @param array $parameters
44
-	 */
45
-	public function __construct($parameters) {
46
-		$this->storage = $parameters['storage'];
47
-		$this->namesCache = new CappedMemoryCache();
48
-	}
49
-
50
-	/**
51
-	 * Returns whether the given string is only made of ASCII characters
52
-	 *
53
-	 * @param string $str string
54
-	 *
55
-	 * @return bool true if the string is all ASCII, false otherwise
56
-	 */
57
-	private function isAscii($str) {
58
-		return (bool) !preg_match('/[\\x80-\\xff]+/', $str);
59
-	}
60
-
61
-	/**
62
-	 * Checks whether the given path exists in NFC or NFD form after checking
63
-	 * each form for each path section and returns the correct form.
64
-	 * If no existing path found, returns the path as it was given.
65
-	 *
66
-	 * @param string $fullPath path to check
67
-	 *
68
-	 * @return string original or converted path
69
-	 */
70
-	private function findPathToUse($fullPath) {
71
-		$cachedPath = $this->namesCache[$fullPath];
72
-		if ($cachedPath !== null) {
73
-			return $cachedPath;
74
-		}
75
-
76
-		$sections = explode('/', $fullPath);
77
-		$path = '';
78
-		foreach ($sections as $section) {
79
-			$convertedPath = $this->findPathToUseLastSection($path, $section);
80
-			if ($convertedPath === null) {
81
-				// no point in continuing if the section was not found, use original path
82
-				return $fullPath;
83
-			}
84
-			$path = $convertedPath . '/';
85
-		}
86
-		$path = rtrim($path, '/');
87
-		return $path;
88
-	}
89
-
90
-	/**
91
-	 * Checks whether the last path section of the given path exists in NFC or NFD form
92
-	 * and returns the correct form. If no existing path found, returns null.
93
-	 *
94
-	 * @param string $basePath base path to check
95
-	 * @param string $lastSection last section of the path to check for NFD/NFC variations
96
-	 *
97
-	 * @return string|null original or converted path, or null if none of the forms was found
98
-	 */
99
-	private function findPathToUseLastSection($basePath, $lastSection) {
100
-		$fullPath = $basePath . $lastSection;
101
-		if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
102
-			$this->namesCache[$fullPath] = $fullPath;
103
-			return $fullPath;
104
-		}
105
-
106
-		// swap encoding
107
-		if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
108
-			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
109
-		} else {
110
-			$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
111
-		}
112
-		$otherFullPath = $basePath . $otherFormPath;
113
-		if ($this->storage->file_exists($otherFullPath)) {
114
-			$this->namesCache[$fullPath] = $otherFullPath;
115
-			return $otherFullPath;
116
-		}
117
-
118
-		// return original path, file did not exist at all
119
-		$this->namesCache[$fullPath] = $fullPath;
120
-		return null;
121
-	}
122
-
123
-	/**
124
-	 * see http://php.net/manual/en/function.mkdir.php
125
-	 *
126
-	 * @param string $path
127
-	 * @return bool
128
-	 */
129
-	public function mkdir($path) {
130
-		// note: no conversion here, method should not be called with non-NFC names!
131
-		$result = $this->storage->mkdir($path);
132
-		if ($result) {
133
-			$this->namesCache[$path] = $path;
134
-		}
135
-		return $result;
136
-	}
137
-
138
-	/**
139
-	 * see http://php.net/manual/en/function.rmdir.php
140
-	 *
141
-	 * @param string $path
142
-	 * @return bool
143
-	 */
144
-	public function rmdir($path) {
145
-		$result = $this->storage->rmdir($this->findPathToUse($path));
146
-		if ($result) {
147
-			unset($this->namesCache[$path]);
148
-		}
149
-		return $result;
150
-	}
151
-
152
-	/**
153
-	 * see http://php.net/manual/en/function.opendir.php
154
-	 *
155
-	 * @param string $path
156
-	 * @return resource
157
-	 */
158
-	public function opendir($path) {
159
-		return $this->storage->opendir($this->findPathToUse($path));
160
-	}
161
-
162
-	/**
163
-	 * see http://php.net/manual/en/function.is_dir.php
164
-	 *
165
-	 * @param string $path
166
-	 * @return bool
167
-	 */
168
-	public function is_dir($path) {
169
-		return $this->storage->is_dir($this->findPathToUse($path));
170
-	}
171
-
172
-	/**
173
-	 * see http://php.net/manual/en/function.is_file.php
174
-	 *
175
-	 * @param string $path
176
-	 * @return bool
177
-	 */
178
-	public function is_file($path) {
179
-		return $this->storage->is_file($this->findPathToUse($path));
180
-	}
181
-
182
-	/**
183
-	 * see http://php.net/manual/en/function.stat.php
184
-	 * only the following keys are required in the result: size and mtime
185
-	 *
186
-	 * @param string $path
187
-	 * @return array
188
-	 */
189
-	public function stat($path) {
190
-		return $this->storage->stat($this->findPathToUse($path));
191
-	}
192
-
193
-	/**
194
-	 * see http://php.net/manual/en/function.filetype.php
195
-	 *
196
-	 * @param string $path
197
-	 * @return bool
198
-	 */
199
-	public function filetype($path) {
200
-		return $this->storage->filetype($this->findPathToUse($path));
201
-	}
202
-
203
-	/**
204
-	 * see http://php.net/manual/en/function.filesize.php
205
-	 * The result for filesize when called on a folder is required to be 0
206
-	 *
207
-	 * @param string $path
208
-	 * @return int
209
-	 */
210
-	public function filesize($path) {
211
-		return $this->storage->filesize($this->findPathToUse($path));
212
-	}
213
-
214
-	/**
215
-	 * check if a file can be created in $path
216
-	 *
217
-	 * @param string $path
218
-	 * @return bool
219
-	 */
220
-	public function isCreatable($path) {
221
-		return $this->storage->isCreatable($this->findPathToUse($path));
222
-	}
223
-
224
-	/**
225
-	 * check if a file can be read
226
-	 *
227
-	 * @param string $path
228
-	 * @return bool
229
-	 */
230
-	public function isReadable($path) {
231
-		return $this->storage->isReadable($this->findPathToUse($path));
232
-	}
233
-
234
-	/**
235
-	 * check if a file can be written to
236
-	 *
237
-	 * @param string $path
238
-	 * @return bool
239
-	 */
240
-	public function isUpdatable($path) {
241
-		return $this->storage->isUpdatable($this->findPathToUse($path));
242
-	}
243
-
244
-	/**
245
-	 * check if a file can be deleted
246
-	 *
247
-	 * @param string $path
248
-	 * @return bool
249
-	 */
250
-	public function isDeletable($path) {
251
-		return $this->storage->isDeletable($this->findPathToUse($path));
252
-	}
253
-
254
-	/**
255
-	 * check if a file can be shared
256
-	 *
257
-	 * @param string $path
258
-	 * @return bool
259
-	 */
260
-	public function isSharable($path) {
261
-		return $this->storage->isSharable($this->findPathToUse($path));
262
-	}
263
-
264
-	/**
265
-	 * get the full permissions of a path.
266
-	 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
267
-	 *
268
-	 * @param string $path
269
-	 * @return int
270
-	 */
271
-	public function getPermissions($path) {
272
-		return $this->storage->getPermissions($this->findPathToUse($path));
273
-	}
274
-
275
-	/**
276
-	 * see http://php.net/manual/en/function.file_exists.php
277
-	 *
278
-	 * @param string $path
279
-	 * @return bool
280
-	 */
281
-	public function file_exists($path) {
282
-		return $this->storage->file_exists($this->findPathToUse($path));
283
-	}
284
-
285
-	/**
286
-	 * see http://php.net/manual/en/function.filemtime.php
287
-	 *
288
-	 * @param string $path
289
-	 * @return int
290
-	 */
291
-	public function filemtime($path) {
292
-		return $this->storage->filemtime($this->findPathToUse($path));
293
-	}
294
-
295
-	/**
296
-	 * see http://php.net/manual/en/function.file_get_contents.php
297
-	 *
298
-	 * @param string $path
299
-	 * @return string
300
-	 */
301
-	public function file_get_contents($path) {
302
-		return $this->storage->file_get_contents($this->findPathToUse($path));
303
-	}
304
-
305
-	/**
306
-	 * see http://php.net/manual/en/function.file_put_contents.php
307
-	 *
308
-	 * @param string $path
309
-	 * @param string $data
310
-	 * @return bool
311
-	 */
312
-	public function file_put_contents($path, $data) {
313
-		return $this->storage->file_put_contents($this->findPathToUse($path), $data);
314
-	}
315
-
316
-	/**
317
-	 * see http://php.net/manual/en/function.unlink.php
318
-	 *
319
-	 * @param string $path
320
-	 * @return bool
321
-	 */
322
-	public function unlink($path) {
323
-		$result = $this->storage->unlink($this->findPathToUse($path));
324
-		if ($result) {
325
-			unset($this->namesCache[$path]);
326
-		}
327
-		return $result;
328
-	}
329
-
330
-	/**
331
-	 * see http://php.net/manual/en/function.rename.php
332
-	 *
333
-	 * @param string $path1
334
-	 * @param string $path2
335
-	 * @return bool
336
-	 */
337
-	public function rename($path1, $path2) {
338
-		// second name always NFC
339
-		return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
340
-	}
341
-
342
-	/**
343
-	 * see http://php.net/manual/en/function.copy.php
344
-	 *
345
-	 * @param string $path1
346
-	 * @param string $path2
347
-	 * @return bool
348
-	 */
349
-	public function copy($path1, $path2) {
350
-		return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
351
-	}
352
-
353
-	/**
354
-	 * see http://php.net/manual/en/function.fopen.php
355
-	 *
356
-	 * @param string $path
357
-	 * @param string $mode
358
-	 * @return resource
359
-	 */
360
-	public function fopen($path, $mode) {
361
-		$result = $this->storage->fopen($this->findPathToUse($path), $mode);
362
-		if ($result && $mode !== 'r' && $mode !== 'rb') {
363
-			unset($this->namesCache[$path]);
364
-		}
365
-		return $result;
366
-	}
367
-
368
-	/**
369
-	 * get the mimetype for a file or folder
370
-	 * The mimetype for a folder is required to be "httpd/unix-directory"
371
-	 *
372
-	 * @param string $path
373
-	 * @return string
374
-	 */
375
-	public function getMimeType($path) {
376
-		return $this->storage->getMimeType($this->findPathToUse($path));
377
-	}
378
-
379
-	/**
380
-	 * see http://php.net/manual/en/function.hash.php
381
-	 *
382
-	 * @param string $type
383
-	 * @param string $path
384
-	 * @param bool $raw
385
-	 * @return string
386
-	 */
387
-	public function hash($type, $path, $raw = false) {
388
-		return $this->storage->hash($type, $this->findPathToUse($path), $raw);
389
-	}
390
-
391
-	/**
392
-	 * see http://php.net/manual/en/function.free_space.php
393
-	 *
394
-	 * @param string $path
395
-	 * @return int
396
-	 */
397
-	public function free_space($path) {
398
-		return $this->storage->free_space($this->findPathToUse($path));
399
-	}
400
-
401
-	/**
402
-	 * search for occurrences of $query in file names
403
-	 *
404
-	 * @param string $query
405
-	 * @return array
406
-	 */
407
-	public function search($query) {
408
-		return $this->storage->search($query);
409
-	}
410
-
411
-	/**
412
-	 * see http://php.net/manual/en/function.touch.php
413
-	 * If the backend does not support the operation, false should be returned
414
-	 *
415
-	 * @param string $path
416
-	 * @param int $mtime
417
-	 * @return bool
418
-	 */
419
-	public function touch($path, $mtime = null) {
420
-		return $this->storage->touch($this->findPathToUse($path), $mtime);
421
-	}
422
-
423
-	/**
424
-	 * get the path to a local version of the file.
425
-	 * The local version of the file can be temporary and doesn't have to be persistent across requests
426
-	 *
427
-	 * @param string $path
428
-	 * @return string
429
-	 */
430
-	public function getLocalFile($path) {
431
-		return $this->storage->getLocalFile($this->findPathToUse($path));
432
-	}
433
-
434
-	/**
435
-	 * check if a file or folder has been updated since $time
436
-	 *
437
-	 * @param string $path
438
-	 * @param int $time
439
-	 * @return bool
440
-	 *
441
-	 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
442
-	 * returning true for other changes in the folder is optional
443
-	 */
444
-	public function hasUpdated($path, $time) {
445
-		return $this->storage->hasUpdated($this->findPathToUse($path), $time);
446
-	}
447
-
448
-	/**
449
-	 * get a cache instance for the storage
450
-	 *
451
-	 * @param string $path
452
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
453
-	 * @return \OC\Files\Cache\Cache
454
-	 */
455
-	public function getCache($path = '', $storage = null) {
456
-		if (!$storage) {
457
-			$storage = $this;
458
-		}
459
-		return $this->storage->getCache($this->findPathToUse($path), $storage);
460
-	}
461
-
462
-	/**
463
-	 * get a scanner instance for the storage
464
-	 *
465
-	 * @param string $path
466
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
467
-	 * @return \OC\Files\Cache\Scanner
468
-	 */
469
-	public function getScanner($path = '', $storage = null) {
470
-		if (!$storage) {
471
-			$storage = $this;
472
-		}
473
-		return $this->storage->getScanner($this->findPathToUse($path), $storage);
474
-	}
475
-
476
-	/**
477
-	 * get the ETag for a file or folder
478
-	 *
479
-	 * @param string $path
480
-	 * @return string
481
-	 */
482
-	public function getETag($path) {
483
-		return $this->storage->getETag($this->findPathToUse($path));
484
-	}
485
-
486
-	/**
487
-	 * @param IStorage $sourceStorage
488
-	 * @param string $sourceInternalPath
489
-	 * @param string $targetInternalPath
490
-	 * @return bool
491
-	 */
492
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
493
-		if ($sourceStorage === $this) {
494
-			return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
495
-		}
496
-
497
-		$result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
498
-		if ($result) {
499
-			unset($this->namesCache[$targetInternalPath]);
500
-		}
501
-		return $result;
502
-	}
503
-
504
-	/**
505
-	 * @param IStorage $sourceStorage
506
-	 * @param string $sourceInternalPath
507
-	 * @param string $targetInternalPath
508
-	 * @return bool
509
-	 */
510
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
511
-		if ($sourceStorage === $this) {
512
-			$result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
513
-			if ($result) {
514
-				unset($this->namesCache[$sourceInternalPath]);
515
-				unset($this->namesCache[$targetInternalPath]);
516
-			}
517
-			return $result;
518
-		}
519
-
520
-		$result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
521
-		if ($result) {
522
-			unset($this->namesCache[$sourceInternalPath]);
523
-			unset($this->namesCache[$targetInternalPath]);
524
-		}
525
-		return $result;
526
-	}
527
-
528
-	/**
529
-	 * @param string $path
530
-	 * @return array
531
-	 */
532
-	public function getMetaData($path) {
533
-		return $this->storage->getMetaData($this->findPathToUse($path));
534
-	}
37
+    /**
38
+     * @var ICache
39
+     */
40
+    private $namesCache;
41
+
42
+    /**
43
+     * @param array $parameters
44
+     */
45
+    public function __construct($parameters) {
46
+        $this->storage = $parameters['storage'];
47
+        $this->namesCache = new CappedMemoryCache();
48
+    }
49
+
50
+    /**
51
+     * Returns whether the given string is only made of ASCII characters
52
+     *
53
+     * @param string $str string
54
+     *
55
+     * @return bool true if the string is all ASCII, false otherwise
56
+     */
57
+    private function isAscii($str) {
58
+        return (bool) !preg_match('/[\\x80-\\xff]+/', $str);
59
+    }
60
+
61
+    /**
62
+     * Checks whether the given path exists in NFC or NFD form after checking
63
+     * each form for each path section and returns the correct form.
64
+     * If no existing path found, returns the path as it was given.
65
+     *
66
+     * @param string $fullPath path to check
67
+     *
68
+     * @return string original or converted path
69
+     */
70
+    private function findPathToUse($fullPath) {
71
+        $cachedPath = $this->namesCache[$fullPath];
72
+        if ($cachedPath !== null) {
73
+            return $cachedPath;
74
+        }
75
+
76
+        $sections = explode('/', $fullPath);
77
+        $path = '';
78
+        foreach ($sections as $section) {
79
+            $convertedPath = $this->findPathToUseLastSection($path, $section);
80
+            if ($convertedPath === null) {
81
+                // no point in continuing if the section was not found, use original path
82
+                return $fullPath;
83
+            }
84
+            $path = $convertedPath . '/';
85
+        }
86
+        $path = rtrim($path, '/');
87
+        return $path;
88
+    }
89
+
90
+    /**
91
+     * Checks whether the last path section of the given path exists in NFC or NFD form
92
+     * and returns the correct form. If no existing path found, returns null.
93
+     *
94
+     * @param string $basePath base path to check
95
+     * @param string $lastSection last section of the path to check for NFD/NFC variations
96
+     *
97
+     * @return string|null original or converted path, or null if none of the forms was found
98
+     */
99
+    private function findPathToUseLastSection($basePath, $lastSection) {
100
+        $fullPath = $basePath . $lastSection;
101
+        if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
102
+            $this->namesCache[$fullPath] = $fullPath;
103
+            return $fullPath;
104
+        }
105
+
106
+        // swap encoding
107
+        if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
108
+            $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
109
+        } else {
110
+            $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
111
+        }
112
+        $otherFullPath = $basePath . $otherFormPath;
113
+        if ($this->storage->file_exists($otherFullPath)) {
114
+            $this->namesCache[$fullPath] = $otherFullPath;
115
+            return $otherFullPath;
116
+        }
117
+
118
+        // return original path, file did not exist at all
119
+        $this->namesCache[$fullPath] = $fullPath;
120
+        return null;
121
+    }
122
+
123
+    /**
124
+     * see http://php.net/manual/en/function.mkdir.php
125
+     *
126
+     * @param string $path
127
+     * @return bool
128
+     */
129
+    public function mkdir($path) {
130
+        // note: no conversion here, method should not be called with non-NFC names!
131
+        $result = $this->storage->mkdir($path);
132
+        if ($result) {
133
+            $this->namesCache[$path] = $path;
134
+        }
135
+        return $result;
136
+    }
137
+
138
+    /**
139
+     * see http://php.net/manual/en/function.rmdir.php
140
+     *
141
+     * @param string $path
142
+     * @return bool
143
+     */
144
+    public function rmdir($path) {
145
+        $result = $this->storage->rmdir($this->findPathToUse($path));
146
+        if ($result) {
147
+            unset($this->namesCache[$path]);
148
+        }
149
+        return $result;
150
+    }
151
+
152
+    /**
153
+     * see http://php.net/manual/en/function.opendir.php
154
+     *
155
+     * @param string $path
156
+     * @return resource
157
+     */
158
+    public function opendir($path) {
159
+        return $this->storage->opendir($this->findPathToUse($path));
160
+    }
161
+
162
+    /**
163
+     * see http://php.net/manual/en/function.is_dir.php
164
+     *
165
+     * @param string $path
166
+     * @return bool
167
+     */
168
+    public function is_dir($path) {
169
+        return $this->storage->is_dir($this->findPathToUse($path));
170
+    }
171
+
172
+    /**
173
+     * see http://php.net/manual/en/function.is_file.php
174
+     *
175
+     * @param string $path
176
+     * @return bool
177
+     */
178
+    public function is_file($path) {
179
+        return $this->storage->is_file($this->findPathToUse($path));
180
+    }
181
+
182
+    /**
183
+     * see http://php.net/manual/en/function.stat.php
184
+     * only the following keys are required in the result: size and mtime
185
+     *
186
+     * @param string $path
187
+     * @return array
188
+     */
189
+    public function stat($path) {
190
+        return $this->storage->stat($this->findPathToUse($path));
191
+    }
192
+
193
+    /**
194
+     * see http://php.net/manual/en/function.filetype.php
195
+     *
196
+     * @param string $path
197
+     * @return bool
198
+     */
199
+    public function filetype($path) {
200
+        return $this->storage->filetype($this->findPathToUse($path));
201
+    }
202
+
203
+    /**
204
+     * see http://php.net/manual/en/function.filesize.php
205
+     * The result for filesize when called on a folder is required to be 0
206
+     *
207
+     * @param string $path
208
+     * @return int
209
+     */
210
+    public function filesize($path) {
211
+        return $this->storage->filesize($this->findPathToUse($path));
212
+    }
213
+
214
+    /**
215
+     * check if a file can be created in $path
216
+     *
217
+     * @param string $path
218
+     * @return bool
219
+     */
220
+    public function isCreatable($path) {
221
+        return $this->storage->isCreatable($this->findPathToUse($path));
222
+    }
223
+
224
+    /**
225
+     * check if a file can be read
226
+     *
227
+     * @param string $path
228
+     * @return bool
229
+     */
230
+    public function isReadable($path) {
231
+        return $this->storage->isReadable($this->findPathToUse($path));
232
+    }
233
+
234
+    /**
235
+     * check if a file can be written to
236
+     *
237
+     * @param string $path
238
+     * @return bool
239
+     */
240
+    public function isUpdatable($path) {
241
+        return $this->storage->isUpdatable($this->findPathToUse($path));
242
+    }
243
+
244
+    /**
245
+     * check if a file can be deleted
246
+     *
247
+     * @param string $path
248
+     * @return bool
249
+     */
250
+    public function isDeletable($path) {
251
+        return $this->storage->isDeletable($this->findPathToUse($path));
252
+    }
253
+
254
+    /**
255
+     * check if a file can be shared
256
+     *
257
+     * @param string $path
258
+     * @return bool
259
+     */
260
+    public function isSharable($path) {
261
+        return $this->storage->isSharable($this->findPathToUse($path));
262
+    }
263
+
264
+    /**
265
+     * get the full permissions of a path.
266
+     * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
267
+     *
268
+     * @param string $path
269
+     * @return int
270
+     */
271
+    public function getPermissions($path) {
272
+        return $this->storage->getPermissions($this->findPathToUse($path));
273
+    }
274
+
275
+    /**
276
+     * see http://php.net/manual/en/function.file_exists.php
277
+     *
278
+     * @param string $path
279
+     * @return bool
280
+     */
281
+    public function file_exists($path) {
282
+        return $this->storage->file_exists($this->findPathToUse($path));
283
+    }
284
+
285
+    /**
286
+     * see http://php.net/manual/en/function.filemtime.php
287
+     *
288
+     * @param string $path
289
+     * @return int
290
+     */
291
+    public function filemtime($path) {
292
+        return $this->storage->filemtime($this->findPathToUse($path));
293
+    }
294
+
295
+    /**
296
+     * see http://php.net/manual/en/function.file_get_contents.php
297
+     *
298
+     * @param string $path
299
+     * @return string
300
+     */
301
+    public function file_get_contents($path) {
302
+        return $this->storage->file_get_contents($this->findPathToUse($path));
303
+    }
304
+
305
+    /**
306
+     * see http://php.net/manual/en/function.file_put_contents.php
307
+     *
308
+     * @param string $path
309
+     * @param string $data
310
+     * @return bool
311
+     */
312
+    public function file_put_contents($path, $data) {
313
+        return $this->storage->file_put_contents($this->findPathToUse($path), $data);
314
+    }
315
+
316
+    /**
317
+     * see http://php.net/manual/en/function.unlink.php
318
+     *
319
+     * @param string $path
320
+     * @return bool
321
+     */
322
+    public function unlink($path) {
323
+        $result = $this->storage->unlink($this->findPathToUse($path));
324
+        if ($result) {
325
+            unset($this->namesCache[$path]);
326
+        }
327
+        return $result;
328
+    }
329
+
330
+    /**
331
+     * see http://php.net/manual/en/function.rename.php
332
+     *
333
+     * @param string $path1
334
+     * @param string $path2
335
+     * @return bool
336
+     */
337
+    public function rename($path1, $path2) {
338
+        // second name always NFC
339
+        return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2));
340
+    }
341
+
342
+    /**
343
+     * see http://php.net/manual/en/function.copy.php
344
+     *
345
+     * @param string $path1
346
+     * @param string $path2
347
+     * @return bool
348
+     */
349
+    public function copy($path1, $path2) {
350
+        return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2));
351
+    }
352
+
353
+    /**
354
+     * see http://php.net/manual/en/function.fopen.php
355
+     *
356
+     * @param string $path
357
+     * @param string $mode
358
+     * @return resource
359
+     */
360
+    public function fopen($path, $mode) {
361
+        $result = $this->storage->fopen($this->findPathToUse($path), $mode);
362
+        if ($result && $mode !== 'r' && $mode !== 'rb') {
363
+            unset($this->namesCache[$path]);
364
+        }
365
+        return $result;
366
+    }
367
+
368
+    /**
369
+     * get the mimetype for a file or folder
370
+     * The mimetype for a folder is required to be "httpd/unix-directory"
371
+     *
372
+     * @param string $path
373
+     * @return string
374
+     */
375
+    public function getMimeType($path) {
376
+        return $this->storage->getMimeType($this->findPathToUse($path));
377
+    }
378
+
379
+    /**
380
+     * see http://php.net/manual/en/function.hash.php
381
+     *
382
+     * @param string $type
383
+     * @param string $path
384
+     * @param bool $raw
385
+     * @return string
386
+     */
387
+    public function hash($type, $path, $raw = false) {
388
+        return $this->storage->hash($type, $this->findPathToUse($path), $raw);
389
+    }
390
+
391
+    /**
392
+     * see http://php.net/manual/en/function.free_space.php
393
+     *
394
+     * @param string $path
395
+     * @return int
396
+     */
397
+    public function free_space($path) {
398
+        return $this->storage->free_space($this->findPathToUse($path));
399
+    }
400
+
401
+    /**
402
+     * search for occurrences of $query in file names
403
+     *
404
+     * @param string $query
405
+     * @return array
406
+     */
407
+    public function search($query) {
408
+        return $this->storage->search($query);
409
+    }
410
+
411
+    /**
412
+     * see http://php.net/manual/en/function.touch.php
413
+     * If the backend does not support the operation, false should be returned
414
+     *
415
+     * @param string $path
416
+     * @param int $mtime
417
+     * @return bool
418
+     */
419
+    public function touch($path, $mtime = null) {
420
+        return $this->storage->touch($this->findPathToUse($path), $mtime);
421
+    }
422
+
423
+    /**
424
+     * get the path to a local version of the file.
425
+     * The local version of the file can be temporary and doesn't have to be persistent across requests
426
+     *
427
+     * @param string $path
428
+     * @return string
429
+     */
430
+    public function getLocalFile($path) {
431
+        return $this->storage->getLocalFile($this->findPathToUse($path));
432
+    }
433
+
434
+    /**
435
+     * check if a file or folder has been updated since $time
436
+     *
437
+     * @param string $path
438
+     * @param int $time
439
+     * @return bool
440
+     *
441
+     * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
442
+     * returning true for other changes in the folder is optional
443
+     */
444
+    public function hasUpdated($path, $time) {
445
+        return $this->storage->hasUpdated($this->findPathToUse($path), $time);
446
+    }
447
+
448
+    /**
449
+     * get a cache instance for the storage
450
+     *
451
+     * @param string $path
452
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
453
+     * @return \OC\Files\Cache\Cache
454
+     */
455
+    public function getCache($path = '', $storage = null) {
456
+        if (!$storage) {
457
+            $storage = $this;
458
+        }
459
+        return $this->storage->getCache($this->findPathToUse($path), $storage);
460
+    }
461
+
462
+    /**
463
+     * get a scanner instance for the storage
464
+     *
465
+     * @param string $path
466
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
467
+     * @return \OC\Files\Cache\Scanner
468
+     */
469
+    public function getScanner($path = '', $storage = null) {
470
+        if (!$storage) {
471
+            $storage = $this;
472
+        }
473
+        return $this->storage->getScanner($this->findPathToUse($path), $storage);
474
+    }
475
+
476
+    /**
477
+     * get the ETag for a file or folder
478
+     *
479
+     * @param string $path
480
+     * @return string
481
+     */
482
+    public function getETag($path) {
483
+        return $this->storage->getETag($this->findPathToUse($path));
484
+    }
485
+
486
+    /**
487
+     * @param IStorage $sourceStorage
488
+     * @param string $sourceInternalPath
489
+     * @param string $targetInternalPath
490
+     * @return bool
491
+     */
492
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
493
+        if ($sourceStorage === $this) {
494
+            return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
495
+        }
496
+
497
+        $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
498
+        if ($result) {
499
+            unset($this->namesCache[$targetInternalPath]);
500
+        }
501
+        return $result;
502
+    }
503
+
504
+    /**
505
+     * @param IStorage $sourceStorage
506
+     * @param string $sourceInternalPath
507
+     * @param string $targetInternalPath
508
+     * @return bool
509
+     */
510
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
511
+        if ($sourceStorage === $this) {
512
+            $result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
513
+            if ($result) {
514
+                unset($this->namesCache[$sourceInternalPath]);
515
+                unset($this->namesCache[$targetInternalPath]);
516
+            }
517
+            return $result;
518
+        }
519
+
520
+        $result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath));
521
+        if ($result) {
522
+            unset($this->namesCache[$sourceInternalPath]);
523
+            unset($this->namesCache[$targetInternalPath]);
524
+        }
525
+        return $result;
526
+    }
527
+
528
+    /**
529
+     * @param string $path
530
+     * @return array
531
+     */
532
+    public function getMetaData($path) {
533
+        return $this->storage->getMetaData($this->findPathToUse($path));
534
+    }
535 535
 }
Please login to merge, or discard this patch.
lib/private/Files/Storage/DAV.php 3 patches
Braces   +5 added lines, -2 removed lines patch added patch discarded remove patch
@@ -90,8 +90,11 @@
 block discarded – undo
90 90
 		if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
91 91
 			$host = $params['host'];
92 92
 			//remove leading http[s], will be generated in createBaseUri()
93
-			if (substr($host, 0, 8) == "https://") $host = substr($host, 8);
94
-			else if (substr($host, 0, 7) == "http://") $host = substr($host, 7);
93
+			if (substr($host, 0, 8) == "https://") {
94
+			    $host = substr($host, 8);
95
+			} else if (substr($host, 0, 7) == "http://") {
96
+			    $host = substr($host, 7);
97
+			}
95 98
 			$this->host = $host;
96 99
 			$this->user = $params['user'];
97 100
 			$this->password = $params['password'];
Please login to merge, or discard this patch.
Indentation   +800 added lines, -800 removed lines patch added patch discarded remove patch
@@ -58,805 +58,805 @@
 block discarded – undo
58 58
  * @package OC\Files\Storage
59 59
  */
60 60
 class DAV extends Common {
61
-	/** @var string */
62
-	protected $password;
63
-	/** @var string */
64
-	protected $user;
65
-	/** @var string */
66
-	protected $authType;
67
-	/** @var string */
68
-	protected $host;
69
-	/** @var bool */
70
-	protected $secure;
71
-	/** @var string */
72
-	protected $root;
73
-	/** @var string */
74
-	protected $certPath;
75
-	/** @var bool */
76
-	protected $ready;
77
-	/** @var Client */
78
-	protected $client;
79
-	/** @var ArrayCache */
80
-	protected $statCache;
81
-	/** @var \OCP\Http\Client\IClientService */
82
-	protected $httpClientService;
83
-
84
-	/**
85
-	 * @param array $params
86
-	 * @throws \Exception
87
-	 */
88
-	public function __construct($params) {
89
-		$this->statCache = new ArrayCache();
90
-		$this->httpClientService = \OC::$server->getHTTPClientService();
91
-		if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
92
-			$host = $params['host'];
93
-			//remove leading http[s], will be generated in createBaseUri()
94
-			if (substr($host, 0, 8) == "https://") $host = substr($host, 8);
95
-			else if (substr($host, 0, 7) == "http://") $host = substr($host, 7);
96
-			$this->host = $host;
97
-			$this->user = $params['user'];
98
-			$this->password = $params['password'];
99
-			if (isset($params['authType'])) {
100
-				$this->authType = $params['authType'];
101
-			}
102
-			if (isset($params['secure'])) {
103
-				if (is_string($params['secure'])) {
104
-					$this->secure = ($params['secure'] === 'true');
105
-				} else {
106
-					$this->secure = (bool)$params['secure'];
107
-				}
108
-			} else {
109
-				$this->secure = false;
110
-			}
111
-			if ($this->secure === true) {
112
-				// inject mock for testing
113
-				$certManager = \OC::$server->getCertificateManager();
114
-				if (is_null($certManager)) { //no user
115
-					$certManager = \OC::$server->getCertificateManager(null);
116
-				}
117
-				$certPath = $certManager->getAbsoluteBundlePath();
118
-				if (file_exists($certPath)) {
119
-					$this->certPath = $certPath;
120
-				}
121
-			}
122
-			$this->root = $params['root'] ?? '/';
123
-			$this->root = '/' . ltrim($this->root, '/');
124
-			$this->root = rtrim($this->root, '/') . '/';
125
-		} else {
126
-			throw new \Exception('Invalid webdav storage configuration');
127
-		}
128
-	}
129
-
130
-	protected function init() {
131
-		if ($this->ready) {
132
-			return;
133
-		}
134
-		$this->ready = true;
135
-
136
-		$settings = [
137
-			'baseUri' => $this->createBaseUri(),
138
-			'userName' => $this->user,
139
-			'password' => $this->password,
140
-		];
141
-		if (isset($this->authType)) {
142
-			$settings['authType'] = $this->authType;
143
-		}
144
-
145
-		$proxy = \OC::$server->getConfig()->getSystemValue('proxy', '');
146
-		if ($proxy !== '') {
147
-			$settings['proxy'] = $proxy;
148
-		}
149
-
150
-		$this->client = new Client($settings);
151
-		$this->client->setThrowExceptions(true);
152
-		if ($this->secure === true && $this->certPath) {
153
-			$this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
154
-		}
155
-	}
156
-
157
-	/**
158
-	 * Clear the stat cache
159
-	 */
160
-	public function clearStatCache() {
161
-		$this->statCache->clear();
162
-	}
163
-
164
-	/** {@inheritdoc} */
165
-	public function getId() {
166
-		return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
167
-	}
168
-
169
-	/** {@inheritdoc} */
170
-	public function createBaseUri() {
171
-		$baseUri = 'http';
172
-		if ($this->secure) {
173
-			$baseUri .= 's';
174
-		}
175
-		$baseUri .= '://' . $this->host . $this->root;
176
-		return $baseUri;
177
-	}
178
-
179
-	/** {@inheritdoc} */
180
-	public function mkdir($path) {
181
-		$this->init();
182
-		$path = $this->cleanPath($path);
183
-		$result = $this->simpleResponse('MKCOL', $path, null, 201);
184
-		if ($result) {
185
-			$this->statCache->set($path, true);
186
-		}
187
-		return $result;
188
-	}
189
-
190
-	/** {@inheritdoc} */
191
-	public function rmdir($path) {
192
-		$this->init();
193
-		$path = $this->cleanPath($path);
194
-		// FIXME: some WebDAV impl return 403 when trying to DELETE
195
-		// a non-empty folder
196
-		$result = $this->simpleResponse('DELETE', $path . '/', null, 204);
197
-		$this->statCache->clear($path . '/');
198
-		$this->statCache->remove($path);
199
-		return $result;
200
-	}
201
-
202
-	/** {@inheritdoc} */
203
-	public function opendir($path) {
204
-		$this->init();
205
-		$path = $this->cleanPath($path);
206
-		try {
207
-			$response = $this->client->propFind(
208
-				$this->encodePath($path),
209
-				['{DAV:}getetag'],
210
-				1
211
-			);
212
-			if ($response === false) {
213
-				return false;
214
-			}
215
-			$content = [];
216
-			$files = array_keys($response);
217
-			array_shift($files); //the first entry is the current directory
218
-
219
-			if (!$this->statCache->hasKey($path)) {
220
-				$this->statCache->set($path, true);
221
-			}
222
-			foreach ($files as $file) {
223
-				$file = urldecode($file);
224
-				// do not store the real entry, we might not have all properties
225
-				if (!$this->statCache->hasKey($path)) {
226
-					$this->statCache->set($file, true);
227
-				}
228
-				$file = basename($file);
229
-				$content[] = $file;
230
-			}
231
-			return IteratorDirectory::wrap($content);
232
-		} catch (\Exception $e) {
233
-			$this->convertException($e, $path);
234
-		}
235
-		return false;
236
-	}
237
-
238
-	/**
239
-	 * Propfind call with cache handling.
240
-	 *
241
-	 * First checks if information is cached.
242
-	 * If not, request it from the server then store to cache.
243
-	 *
244
-	 * @param string $path path to propfind
245
-	 *
246
-	 * @return array|boolean propfind response or false if the entry was not found
247
-	 *
248
-	 * @throws ClientHttpException
249
-	 */
250
-	protected function propfind($path) {
251
-		$path = $this->cleanPath($path);
252
-		$cachedResponse = $this->statCache->get($path);
253
-		// we either don't know it, or we know it exists but need more details
254
-		if (is_null($cachedResponse) || $cachedResponse === true) {
255
-			$this->init();
256
-			try {
257
-				$response = $this->client->propFind(
258
-					$this->encodePath($path),
259
-					array(
260
-						'{DAV:}getlastmodified',
261
-						'{DAV:}getcontentlength',
262
-						'{DAV:}getcontenttype',
263
-						'{http://owncloud.org/ns}permissions',
264
-						'{http://open-collaboration-services.org/ns}share-permissions',
265
-						'{DAV:}resourcetype',
266
-						'{DAV:}getetag',
267
-					)
268
-				);
269
-				$this->statCache->set($path, $response);
270
-			} catch (ClientHttpException $e) {
271
-				if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) {
272
-					$this->statCache->clear($path . '/');
273
-					$this->statCache->set($path, false);
274
-					return false;
275
-				}
276
-				$this->convertException($e, $path);
277
-			} catch (\Exception $e) {
278
-				$this->convertException($e, $path);
279
-			}
280
-		} else {
281
-			$response = $cachedResponse;
282
-		}
283
-		return $response;
284
-	}
285
-
286
-	/** {@inheritdoc} */
287
-	public function filetype($path) {
288
-		try {
289
-			$response = $this->propfind($path);
290
-			if ($response === false) {
291
-				return false;
292
-			}
293
-			$responseType = [];
294
-			if (isset($response["{DAV:}resourcetype"])) {
295
-				/** @var ResourceType[] $response */
296
-				$responseType = $response["{DAV:}resourcetype"]->getValue();
297
-			}
298
-			return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
299
-		} catch (\Exception $e) {
300
-			$this->convertException($e, $path);
301
-		}
302
-		return false;
303
-	}
304
-
305
-	/** {@inheritdoc} */
306
-	public function file_exists($path) {
307
-		try {
308
-			$path = $this->cleanPath($path);
309
-			$cachedState = $this->statCache->get($path);
310
-			if ($cachedState === false) {
311
-				// we know the file doesn't exist
312
-				return false;
313
-			} else if (!is_null($cachedState)) {
314
-				return true;
315
-			}
316
-			// need to get from server
317
-			return ($this->propfind($path) !== false);
318
-		} catch (\Exception $e) {
319
-			$this->convertException($e, $path);
320
-		}
321
-		return false;
322
-	}
323
-
324
-	/** {@inheritdoc} */
325
-	public function unlink($path) {
326
-		$this->init();
327
-		$path = $this->cleanPath($path);
328
-		$result = $this->simpleResponse('DELETE', $path, null, 204);
329
-		$this->statCache->clear($path . '/');
330
-		$this->statCache->remove($path);
331
-		return $result;
332
-	}
333
-
334
-	/** {@inheritdoc} */
335
-	public function fopen($path, $mode) {
336
-		$this->init();
337
-		$path = $this->cleanPath($path);
338
-		switch ($mode) {
339
-			case 'r':
340
-			case 'rb':
341
-				try {
342
-					$response = $this->httpClientService
343
-						->newClient()
344
-						->get($this->createBaseUri() . $this->encodePath($path), [
345
-							'auth' => [$this->user, $this->password],
346
-							'stream' => true
347
-						]);
348
-				} catch (\GuzzleHttp\Exception\ClientException $e) {
349
-					if ($e->getResponse() instanceof ResponseInterface
350
-						&& $e->getResponse()->getStatusCode() === 404) {
351
-						return false;
352
-					} else {
353
-						throw $e;
354
-					}
355
-				}
356
-
357
-				if ($response->getStatusCode() !== Http::STATUS_OK) {
358
-					if ($response->getStatusCode() === Http::STATUS_LOCKED) {
359
-						throw new \OCP\Lock\LockedException($path);
360
-					} else {
361
-						Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), ILogger::ERROR);
362
-					}
363
-				}
364
-
365
-				return $response->getBody();
366
-			case 'w':
367
-			case 'wb':
368
-			case 'a':
369
-			case 'ab':
370
-			case 'r+':
371
-			case 'w+':
372
-			case 'wb+':
373
-			case 'a+':
374
-			case 'x':
375
-			case 'x+':
376
-			case 'c':
377
-			case 'c+':
378
-				//emulate these
379
-				$tempManager = \OC::$server->getTempManager();
380
-				if (strrpos($path, '.') !== false) {
381
-					$ext = substr($path, strrpos($path, '.'));
382
-				} else {
383
-					$ext = '';
384
-				}
385
-				if ($this->file_exists($path)) {
386
-					if (!$this->isUpdatable($path)) {
387
-						return false;
388
-					}
389
-					if ($mode === 'w' or $mode === 'w+') {
390
-						$tmpFile = $tempManager->getTemporaryFile($ext);
391
-					} else {
392
-						$tmpFile = $this->getCachedFile($path);
393
-					}
394
-				} else {
395
-					if (!$this->isCreatable(dirname($path))) {
396
-						return false;
397
-					}
398
-					$tmpFile = $tempManager->getTemporaryFile($ext);
399
-				}
400
-				$handle = fopen($tmpFile, $mode);
401
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
402
-					$this->writeBack($tmpFile, $path);
403
-				});
404
-		}
405
-	}
406
-
407
-	/**
408
-	 * @param string $tmpFile
409
-	 */
410
-	public function writeBack($tmpFile, $path) {
411
-		$this->uploadFile($tmpFile, $path);
412
-		unlink($tmpFile);
413
-	}
414
-
415
-	/** {@inheritdoc} */
416
-	public function free_space($path) {
417
-		$this->init();
418
-		$path = $this->cleanPath($path);
419
-		try {
420
-			// TODO: cacheable ?
421
-			$response = $this->client->propfind($this->encodePath($path), ['{DAV:}quota-available-bytes']);
422
-			if ($response === false) {
423
-				return FileInfo::SPACE_UNKNOWN;
424
-			}
425
-			if (isset($response['{DAV:}quota-available-bytes'])) {
426
-				return (int)$response['{DAV:}quota-available-bytes'];
427
-			} else {
428
-				return FileInfo::SPACE_UNKNOWN;
429
-			}
430
-		} catch (\Exception $e) {
431
-			return FileInfo::SPACE_UNKNOWN;
432
-		}
433
-	}
434
-
435
-	/** {@inheritdoc} */
436
-	public function touch($path, $mtime = null) {
437
-		$this->init();
438
-		if (is_null($mtime)) {
439
-			$mtime = time();
440
-		}
441
-		$path = $this->cleanPath($path);
442
-
443
-		// if file exists, update the mtime, else create a new empty file
444
-		if ($this->file_exists($path)) {
445
-			try {
446
-				$this->statCache->remove($path);
447
-				$this->client->proppatch($this->encodePath($path), ['{DAV:}lastmodified' => $mtime]);
448
-				// non-owncloud clients might not have accepted the property, need to recheck it
449
-				$response = $this->client->propfind($this->encodePath($path), ['{DAV:}getlastmodified'], 0);
450
-				if ($response === false) {
451
-					return false;
452
-				}
453
-				if (isset($response['{DAV:}getlastmodified'])) {
454
-					$remoteMtime = strtotime($response['{DAV:}getlastmodified']);
455
-					if ($remoteMtime !== $mtime) {
456
-						// server has not accepted the mtime
457
-						return false;
458
-					}
459
-				}
460
-			} catch (ClientHttpException $e) {
461
-				if ($e->getHttpStatus() === 501) {
462
-					return false;
463
-				}
464
-				$this->convertException($e, $path);
465
-				return false;
466
-			} catch (\Exception $e) {
467
-				$this->convertException($e, $path);
468
-				return false;
469
-			}
470
-		} else {
471
-			$this->file_put_contents($path, '');
472
-		}
473
-		return true;
474
-	}
475
-
476
-	/**
477
-	 * @param string $path
478
-	 * @param string $data
479
-	 * @return int
480
-	 */
481
-	public function file_put_contents($path, $data) {
482
-		$path = $this->cleanPath($path);
483
-		$result = parent::file_put_contents($path, $data);
484
-		$this->statCache->remove($path);
485
-		return $result;
486
-	}
487
-
488
-	/**
489
-	 * @param string $path
490
-	 * @param string $target
491
-	 */
492
-	protected function uploadFile($path, $target) {
493
-		$this->init();
494
-
495
-		// invalidate
496
-		$target = $this->cleanPath($target);
497
-		$this->statCache->remove($target);
498
-		$source = fopen($path, 'r');
499
-
500
-		$this->httpClientService
501
-			->newClient()
502
-			->put($this->createBaseUri() . $this->encodePath($target), [
503
-				'body' => $source,
504
-				'auth' => [$this->user, $this->password]
505
-			]);
506
-
507
-		$this->removeCachedFile($target);
508
-	}
509
-
510
-	/** {@inheritdoc} */
511
-	public function rename($path1, $path2) {
512
-		$this->init();
513
-		$path1 = $this->cleanPath($path1);
514
-		$path2 = $this->cleanPath($path2);
515
-		try {
516
-			// overwrite directory ?
517
-			if ($this->is_dir($path2)) {
518
-				// needs trailing slash in destination
519
-				$path2 = rtrim($path2, '/') . '/';
520
-			}
521
-			$this->client->request(
522
-				'MOVE',
523
-				$this->encodePath($path1),
524
-				null,
525
-				[
526
-					'Destination' => $this->createBaseUri() . $this->encodePath($path2),
527
-				]
528
-			);
529
-			$this->statCache->clear($path1 . '/');
530
-			$this->statCache->clear($path2 . '/');
531
-			$this->statCache->set($path1, false);
532
-			$this->statCache->set($path2, true);
533
-			$this->removeCachedFile($path1);
534
-			$this->removeCachedFile($path2);
535
-			return true;
536
-		} catch (\Exception $e) {
537
-			$this->convertException($e);
538
-		}
539
-		return false;
540
-	}
541
-
542
-	/** {@inheritdoc} */
543
-	public function copy($path1, $path2) {
544
-		$this->init();
545
-		$path1 = $this->cleanPath($path1);
546
-		$path2 = $this->cleanPath($path2);
547
-		try {
548
-			// overwrite directory ?
549
-			if ($this->is_dir($path2)) {
550
-				// needs trailing slash in destination
551
-				$path2 = rtrim($path2, '/') . '/';
552
-			}
553
-			$this->client->request(
554
-				'COPY',
555
-				$this->encodePath($path1),
556
-				null,
557
-				[
558
-					'Destination' => $this->createBaseUri() . $this->encodePath($path2),
559
-				]
560
-			);
561
-			$this->statCache->clear($path2 . '/');
562
-			$this->statCache->set($path2, true);
563
-			$this->removeCachedFile($path2);
564
-			return true;
565
-		} catch (\Exception $e) {
566
-			$this->convertException($e);
567
-		}
568
-		return false;
569
-	}
570
-
571
-	/** {@inheritdoc} */
572
-	public function stat($path) {
573
-		try {
574
-			$response = $this->propfind($path);
575
-			if (!$response) {
576
-				return false;
577
-			}
578
-			return [
579
-				'mtime' => strtotime($response['{DAV:}getlastmodified']),
580
-				'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0,
581
-			];
582
-		} catch (\Exception $e) {
583
-			$this->convertException($e, $path);
584
-		}
585
-		return array();
586
-	}
587
-
588
-	/** {@inheritdoc} */
589
-	public function getMimeType($path) {
590
-		$remoteMimetype = $this->getMimeTypeFromRemote($path);
591
-		if ($remoteMimetype === 'application/octet-stream') {
592
-			return \OC::$server->getMimeTypeDetector()->detectPath($path);
593
-		} else {
594
-			return $remoteMimetype;
595
-		}
596
-	}
597
-
598
-	public function getMimeTypeFromRemote($path) {
599
-		try {
600
-			$response = $this->propfind($path);
601
-			if ($response === false) {
602
-				return false;
603
-			}
604
-			$responseType = [];
605
-			if (isset($response["{DAV:}resourcetype"])) {
606
-				/** @var ResourceType[] $response */
607
-				$responseType = $response["{DAV:}resourcetype"]->getValue();
608
-			}
609
-			$type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
610
-			if ($type == 'dir') {
611
-				return 'httpd/unix-directory';
612
-			} elseif (isset($response['{DAV:}getcontenttype'])) {
613
-				return $response['{DAV:}getcontenttype'];
614
-			} else {
615
-				return 'application/octet-stream';
616
-			}
617
-		} catch (\Exception $e) {
618
-			return false;
619
-		}
620
-	}
621
-
622
-	/**
623
-	 * @param string $path
624
-	 * @return string
625
-	 */
626
-	public function cleanPath($path) {
627
-		if ($path === '') {
628
-			return $path;
629
-		}
630
-		$path = Filesystem::normalizePath($path);
631
-		// remove leading slash
632
-		return substr($path, 1);
633
-	}
634
-
635
-	/**
636
-	 * URL encodes the given path but keeps the slashes
637
-	 *
638
-	 * @param string $path to encode
639
-	 * @return string encoded path
640
-	 */
641
-	protected function encodePath($path) {
642
-		// slashes need to stay
643
-		return str_replace('%2F', '/', rawurlencode($path));
644
-	}
645
-
646
-	/**
647
-	 * @param string $method
648
-	 * @param string $path
649
-	 * @param string|resource|null $body
650
-	 * @param int $expected
651
-	 * @return bool
652
-	 * @throws StorageInvalidException
653
-	 * @throws StorageNotAvailableException
654
-	 */
655
-	protected function simpleResponse($method, $path, $body, $expected) {
656
-		$path = $this->cleanPath($path);
657
-		try {
658
-			$response = $this->client->request($method, $this->encodePath($path), $body);
659
-			return $response['statusCode'] == $expected;
660
-		} catch (ClientHttpException $e) {
661
-			if ($e->getHttpStatus() === 404 && $method === 'DELETE') {
662
-				$this->statCache->clear($path . '/');
663
-				$this->statCache->set($path, false);
664
-				return false;
665
-			}
666
-
667
-			$this->convertException($e, $path);
668
-		} catch (\Exception $e) {
669
-			$this->convertException($e, $path);
670
-		}
671
-		return false;
672
-	}
673
-
674
-	/**
675
-	 * check if curl is installed
676
-	 */
677
-	public static function checkDependencies() {
678
-		return true;
679
-	}
680
-
681
-	/** {@inheritdoc} */
682
-	public function isUpdatable($path) {
683
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
684
-	}
685
-
686
-	/** {@inheritdoc} */
687
-	public function isCreatable($path) {
688
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
689
-	}
690
-
691
-	/** {@inheritdoc} */
692
-	public function isSharable($path) {
693
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
694
-	}
695
-
696
-	/** {@inheritdoc} */
697
-	public function isDeletable($path) {
698
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
699
-	}
700
-
701
-	/** {@inheritdoc} */
702
-	public function getPermissions($path) {
703
-		$this->init();
704
-		$path = $this->cleanPath($path);
705
-		$response = $this->propfind($path);
706
-		if ($response === false) {
707
-			return 0;
708
-		}
709
-		if (isset($response['{http://owncloud.org/ns}permissions'])) {
710
-			return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
711
-		} else if ($this->is_dir($path)) {
712
-			return Constants::PERMISSION_ALL;
713
-		} else if ($this->file_exists($path)) {
714
-			return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
715
-		} else {
716
-			return 0;
717
-		}
718
-	}
719
-
720
-	/** {@inheritdoc} */
721
-	public function getETag($path) {
722
-		$this->init();
723
-		$path = $this->cleanPath($path);
724
-		$response = $this->propfind($path);
725
-		if ($response === false) {
726
-			return null;
727
-		}
728
-		if (isset($response['{DAV:}getetag'])) {
729
-			$etag = trim($response['{DAV:}getetag'], '"');
730
-			if (strlen($etag) > 40) {
731
-				$etag = md5($etag);
732
-			}
733
-			return $etag;
734
-		}
735
-		return parent::getEtag($path);
736
-	}
737
-
738
-	/**
739
-	 * @param string $permissionsString
740
-	 * @return int
741
-	 */
742
-	protected function parsePermissions($permissionsString) {
743
-		$permissions = Constants::PERMISSION_READ;
744
-		if (strpos($permissionsString, 'R') !== false) {
745
-			$permissions |= Constants::PERMISSION_SHARE;
746
-		}
747
-		if (strpos($permissionsString, 'D') !== false) {
748
-			$permissions |= Constants::PERMISSION_DELETE;
749
-		}
750
-		if (strpos($permissionsString, 'W') !== false) {
751
-			$permissions |= Constants::PERMISSION_UPDATE;
752
-		}
753
-		if (strpos($permissionsString, 'CK') !== false) {
754
-			$permissions |= Constants::PERMISSION_CREATE;
755
-			$permissions |= Constants::PERMISSION_UPDATE;
756
-		}
757
-		return $permissions;
758
-	}
759
-
760
-	/**
761
-	 * check if a file or folder has been updated since $time
762
-	 *
763
-	 * @param string $path
764
-	 * @param int $time
765
-	 * @throws \OCP\Files\StorageNotAvailableException
766
-	 * @return bool
767
-	 */
768
-	public function hasUpdated($path, $time) {
769
-		$this->init();
770
-		$path = $this->cleanPath($path);
771
-		try {
772
-			// force refresh for $path
773
-			$this->statCache->remove($path);
774
-			$response = $this->propfind($path);
775
-			if ($response === false) {
776
-				if ($path === '') {
777
-					// if root is gone it means the storage is not available
778
-					throw new StorageNotAvailableException('root is gone');
779
-				}
780
-				return false;
781
-			}
782
-			if (isset($response['{DAV:}getetag'])) {
783
-				$cachedData = $this->getCache()->get($path);
784
-				$etag = null;
785
-				if (isset($response['{DAV:}getetag'])) {
786
-					$etag = trim($response['{DAV:}getetag'], '"');
787
-				}
788
-				if (!empty($etag) && $cachedData['etag'] !== $etag) {
789
-					return true;
790
-				} else if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
791
-					$sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions'];
792
-					return $sharePermissions !== $cachedData['permissions'];
793
-				} else if (isset($response['{http://owncloud.org/ns}permissions'])) {
794
-					$permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
795
-					return $permissions !== $cachedData['permissions'];
796
-				} else {
797
-					return false;
798
-				}
799
-			} else {
800
-				$remoteMtime = strtotime($response['{DAV:}getlastmodified']);
801
-				return $remoteMtime > $time;
802
-			}
803
-		} catch (ClientHttpException $e) {
804
-			if ($e->getHttpStatus() === 405) {
805
-				if ($path === '') {
806
-					// if root is gone it means the storage is not available
807
-					throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
808
-				}
809
-				return false;
810
-			}
811
-			$this->convertException($e, $path);
812
-			return false;
813
-		} catch (\Exception $e) {
814
-			$this->convertException($e, $path);
815
-			return false;
816
-		}
817
-	}
818
-
819
-	/**
820
-	 * Interpret the given exception and decide whether it is due to an
821
-	 * unavailable storage, invalid storage or other.
822
-	 * This will either throw StorageInvalidException, StorageNotAvailableException
823
-	 * or do nothing.
824
-	 *
825
-	 * @param Exception $e sabre exception
826
-	 * @param string $path optional path from the operation
827
-	 *
828
-	 * @throws StorageInvalidException if the storage is invalid, for example
829
-	 * when the authentication expired or is invalid
830
-	 * @throws StorageNotAvailableException if the storage is not available,
831
-	 * which might be temporary
832
-	 */
833
-	protected function convertException(Exception $e, $path = '') {
834
-		\OC::$server->getLogger()->logException($e, ['app' => 'files_external', 'level' => ILogger::DEBUG]);
835
-		if ($e instanceof ClientHttpException) {
836
-			if ($e->getHttpStatus() === Http::STATUS_LOCKED) {
837
-				throw new \OCP\Lock\LockedException($path);
838
-			}
839
-			if ($e->getHttpStatus() === Http::STATUS_UNAUTHORIZED) {
840
-				// either password was changed or was invalid all along
841
-				throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage());
842
-			} else if ($e->getHttpStatus() === Http::STATUS_METHOD_NOT_ALLOWED) {
843
-				// ignore exception for MethodNotAllowed, false will be returned
844
-				return;
845
-			}
846
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
847
-		} else if ($e instanceof ClientException) {
848
-			// connection timeout or refused, server could be temporarily down
849
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
850
-		} else if ($e instanceof \InvalidArgumentException) {
851
-			// parse error because the server returned HTML instead of XML,
852
-			// possibly temporarily down
853
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
854
-		} else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
855
-			// rethrow
856
-			throw $e;
857
-		}
858
-
859
-		// TODO: only log for now, but in the future need to wrap/rethrow exception
860
-	}
61
+    /** @var string */
62
+    protected $password;
63
+    /** @var string */
64
+    protected $user;
65
+    /** @var string */
66
+    protected $authType;
67
+    /** @var string */
68
+    protected $host;
69
+    /** @var bool */
70
+    protected $secure;
71
+    /** @var string */
72
+    protected $root;
73
+    /** @var string */
74
+    protected $certPath;
75
+    /** @var bool */
76
+    protected $ready;
77
+    /** @var Client */
78
+    protected $client;
79
+    /** @var ArrayCache */
80
+    protected $statCache;
81
+    /** @var \OCP\Http\Client\IClientService */
82
+    protected $httpClientService;
83
+
84
+    /**
85
+     * @param array $params
86
+     * @throws \Exception
87
+     */
88
+    public function __construct($params) {
89
+        $this->statCache = new ArrayCache();
90
+        $this->httpClientService = \OC::$server->getHTTPClientService();
91
+        if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
92
+            $host = $params['host'];
93
+            //remove leading http[s], will be generated in createBaseUri()
94
+            if (substr($host, 0, 8) == "https://") $host = substr($host, 8);
95
+            else if (substr($host, 0, 7) == "http://") $host = substr($host, 7);
96
+            $this->host = $host;
97
+            $this->user = $params['user'];
98
+            $this->password = $params['password'];
99
+            if (isset($params['authType'])) {
100
+                $this->authType = $params['authType'];
101
+            }
102
+            if (isset($params['secure'])) {
103
+                if (is_string($params['secure'])) {
104
+                    $this->secure = ($params['secure'] === 'true');
105
+                } else {
106
+                    $this->secure = (bool)$params['secure'];
107
+                }
108
+            } else {
109
+                $this->secure = false;
110
+            }
111
+            if ($this->secure === true) {
112
+                // inject mock for testing
113
+                $certManager = \OC::$server->getCertificateManager();
114
+                if (is_null($certManager)) { //no user
115
+                    $certManager = \OC::$server->getCertificateManager(null);
116
+                }
117
+                $certPath = $certManager->getAbsoluteBundlePath();
118
+                if (file_exists($certPath)) {
119
+                    $this->certPath = $certPath;
120
+                }
121
+            }
122
+            $this->root = $params['root'] ?? '/';
123
+            $this->root = '/' . ltrim($this->root, '/');
124
+            $this->root = rtrim($this->root, '/') . '/';
125
+        } else {
126
+            throw new \Exception('Invalid webdav storage configuration');
127
+        }
128
+    }
129
+
130
+    protected function init() {
131
+        if ($this->ready) {
132
+            return;
133
+        }
134
+        $this->ready = true;
135
+
136
+        $settings = [
137
+            'baseUri' => $this->createBaseUri(),
138
+            'userName' => $this->user,
139
+            'password' => $this->password,
140
+        ];
141
+        if (isset($this->authType)) {
142
+            $settings['authType'] = $this->authType;
143
+        }
144
+
145
+        $proxy = \OC::$server->getConfig()->getSystemValue('proxy', '');
146
+        if ($proxy !== '') {
147
+            $settings['proxy'] = $proxy;
148
+        }
149
+
150
+        $this->client = new Client($settings);
151
+        $this->client->setThrowExceptions(true);
152
+        if ($this->secure === true && $this->certPath) {
153
+            $this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
154
+        }
155
+    }
156
+
157
+    /**
158
+     * Clear the stat cache
159
+     */
160
+    public function clearStatCache() {
161
+        $this->statCache->clear();
162
+    }
163
+
164
+    /** {@inheritdoc} */
165
+    public function getId() {
166
+        return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
167
+    }
168
+
169
+    /** {@inheritdoc} */
170
+    public function createBaseUri() {
171
+        $baseUri = 'http';
172
+        if ($this->secure) {
173
+            $baseUri .= 's';
174
+        }
175
+        $baseUri .= '://' . $this->host . $this->root;
176
+        return $baseUri;
177
+    }
178
+
179
+    /** {@inheritdoc} */
180
+    public function mkdir($path) {
181
+        $this->init();
182
+        $path = $this->cleanPath($path);
183
+        $result = $this->simpleResponse('MKCOL', $path, null, 201);
184
+        if ($result) {
185
+            $this->statCache->set($path, true);
186
+        }
187
+        return $result;
188
+    }
189
+
190
+    /** {@inheritdoc} */
191
+    public function rmdir($path) {
192
+        $this->init();
193
+        $path = $this->cleanPath($path);
194
+        // FIXME: some WebDAV impl return 403 when trying to DELETE
195
+        // a non-empty folder
196
+        $result = $this->simpleResponse('DELETE', $path . '/', null, 204);
197
+        $this->statCache->clear($path . '/');
198
+        $this->statCache->remove($path);
199
+        return $result;
200
+    }
201
+
202
+    /** {@inheritdoc} */
203
+    public function opendir($path) {
204
+        $this->init();
205
+        $path = $this->cleanPath($path);
206
+        try {
207
+            $response = $this->client->propFind(
208
+                $this->encodePath($path),
209
+                ['{DAV:}getetag'],
210
+                1
211
+            );
212
+            if ($response === false) {
213
+                return false;
214
+            }
215
+            $content = [];
216
+            $files = array_keys($response);
217
+            array_shift($files); //the first entry is the current directory
218
+
219
+            if (!$this->statCache->hasKey($path)) {
220
+                $this->statCache->set($path, true);
221
+            }
222
+            foreach ($files as $file) {
223
+                $file = urldecode($file);
224
+                // do not store the real entry, we might not have all properties
225
+                if (!$this->statCache->hasKey($path)) {
226
+                    $this->statCache->set($file, true);
227
+                }
228
+                $file = basename($file);
229
+                $content[] = $file;
230
+            }
231
+            return IteratorDirectory::wrap($content);
232
+        } catch (\Exception $e) {
233
+            $this->convertException($e, $path);
234
+        }
235
+        return false;
236
+    }
237
+
238
+    /**
239
+     * Propfind call with cache handling.
240
+     *
241
+     * First checks if information is cached.
242
+     * If not, request it from the server then store to cache.
243
+     *
244
+     * @param string $path path to propfind
245
+     *
246
+     * @return array|boolean propfind response or false if the entry was not found
247
+     *
248
+     * @throws ClientHttpException
249
+     */
250
+    protected function propfind($path) {
251
+        $path = $this->cleanPath($path);
252
+        $cachedResponse = $this->statCache->get($path);
253
+        // we either don't know it, or we know it exists but need more details
254
+        if (is_null($cachedResponse) || $cachedResponse === true) {
255
+            $this->init();
256
+            try {
257
+                $response = $this->client->propFind(
258
+                    $this->encodePath($path),
259
+                    array(
260
+                        '{DAV:}getlastmodified',
261
+                        '{DAV:}getcontentlength',
262
+                        '{DAV:}getcontenttype',
263
+                        '{http://owncloud.org/ns}permissions',
264
+                        '{http://open-collaboration-services.org/ns}share-permissions',
265
+                        '{DAV:}resourcetype',
266
+                        '{DAV:}getetag',
267
+                    )
268
+                );
269
+                $this->statCache->set($path, $response);
270
+            } catch (ClientHttpException $e) {
271
+                if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) {
272
+                    $this->statCache->clear($path . '/');
273
+                    $this->statCache->set($path, false);
274
+                    return false;
275
+                }
276
+                $this->convertException($e, $path);
277
+            } catch (\Exception $e) {
278
+                $this->convertException($e, $path);
279
+            }
280
+        } else {
281
+            $response = $cachedResponse;
282
+        }
283
+        return $response;
284
+    }
285
+
286
+    /** {@inheritdoc} */
287
+    public function filetype($path) {
288
+        try {
289
+            $response = $this->propfind($path);
290
+            if ($response === false) {
291
+                return false;
292
+            }
293
+            $responseType = [];
294
+            if (isset($response["{DAV:}resourcetype"])) {
295
+                /** @var ResourceType[] $response */
296
+                $responseType = $response["{DAV:}resourcetype"]->getValue();
297
+            }
298
+            return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
299
+        } catch (\Exception $e) {
300
+            $this->convertException($e, $path);
301
+        }
302
+        return false;
303
+    }
304
+
305
+    /** {@inheritdoc} */
306
+    public function file_exists($path) {
307
+        try {
308
+            $path = $this->cleanPath($path);
309
+            $cachedState = $this->statCache->get($path);
310
+            if ($cachedState === false) {
311
+                // we know the file doesn't exist
312
+                return false;
313
+            } else if (!is_null($cachedState)) {
314
+                return true;
315
+            }
316
+            // need to get from server
317
+            return ($this->propfind($path) !== false);
318
+        } catch (\Exception $e) {
319
+            $this->convertException($e, $path);
320
+        }
321
+        return false;
322
+    }
323
+
324
+    /** {@inheritdoc} */
325
+    public function unlink($path) {
326
+        $this->init();
327
+        $path = $this->cleanPath($path);
328
+        $result = $this->simpleResponse('DELETE', $path, null, 204);
329
+        $this->statCache->clear($path . '/');
330
+        $this->statCache->remove($path);
331
+        return $result;
332
+    }
333
+
334
+    /** {@inheritdoc} */
335
+    public function fopen($path, $mode) {
336
+        $this->init();
337
+        $path = $this->cleanPath($path);
338
+        switch ($mode) {
339
+            case 'r':
340
+            case 'rb':
341
+                try {
342
+                    $response = $this->httpClientService
343
+                        ->newClient()
344
+                        ->get($this->createBaseUri() . $this->encodePath($path), [
345
+                            'auth' => [$this->user, $this->password],
346
+                            'stream' => true
347
+                        ]);
348
+                } catch (\GuzzleHttp\Exception\ClientException $e) {
349
+                    if ($e->getResponse() instanceof ResponseInterface
350
+                        && $e->getResponse()->getStatusCode() === 404) {
351
+                        return false;
352
+                    } else {
353
+                        throw $e;
354
+                    }
355
+                }
356
+
357
+                if ($response->getStatusCode() !== Http::STATUS_OK) {
358
+                    if ($response->getStatusCode() === Http::STATUS_LOCKED) {
359
+                        throw new \OCP\Lock\LockedException($path);
360
+                    } else {
361
+                        Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), ILogger::ERROR);
362
+                    }
363
+                }
364
+
365
+                return $response->getBody();
366
+            case 'w':
367
+            case 'wb':
368
+            case 'a':
369
+            case 'ab':
370
+            case 'r+':
371
+            case 'w+':
372
+            case 'wb+':
373
+            case 'a+':
374
+            case 'x':
375
+            case 'x+':
376
+            case 'c':
377
+            case 'c+':
378
+                //emulate these
379
+                $tempManager = \OC::$server->getTempManager();
380
+                if (strrpos($path, '.') !== false) {
381
+                    $ext = substr($path, strrpos($path, '.'));
382
+                } else {
383
+                    $ext = '';
384
+                }
385
+                if ($this->file_exists($path)) {
386
+                    if (!$this->isUpdatable($path)) {
387
+                        return false;
388
+                    }
389
+                    if ($mode === 'w' or $mode === 'w+') {
390
+                        $tmpFile = $tempManager->getTemporaryFile($ext);
391
+                    } else {
392
+                        $tmpFile = $this->getCachedFile($path);
393
+                    }
394
+                } else {
395
+                    if (!$this->isCreatable(dirname($path))) {
396
+                        return false;
397
+                    }
398
+                    $tmpFile = $tempManager->getTemporaryFile($ext);
399
+                }
400
+                $handle = fopen($tmpFile, $mode);
401
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
402
+                    $this->writeBack($tmpFile, $path);
403
+                });
404
+        }
405
+    }
406
+
407
+    /**
408
+     * @param string $tmpFile
409
+     */
410
+    public function writeBack($tmpFile, $path) {
411
+        $this->uploadFile($tmpFile, $path);
412
+        unlink($tmpFile);
413
+    }
414
+
415
+    /** {@inheritdoc} */
416
+    public function free_space($path) {
417
+        $this->init();
418
+        $path = $this->cleanPath($path);
419
+        try {
420
+            // TODO: cacheable ?
421
+            $response = $this->client->propfind($this->encodePath($path), ['{DAV:}quota-available-bytes']);
422
+            if ($response === false) {
423
+                return FileInfo::SPACE_UNKNOWN;
424
+            }
425
+            if (isset($response['{DAV:}quota-available-bytes'])) {
426
+                return (int)$response['{DAV:}quota-available-bytes'];
427
+            } else {
428
+                return FileInfo::SPACE_UNKNOWN;
429
+            }
430
+        } catch (\Exception $e) {
431
+            return FileInfo::SPACE_UNKNOWN;
432
+        }
433
+    }
434
+
435
+    /** {@inheritdoc} */
436
+    public function touch($path, $mtime = null) {
437
+        $this->init();
438
+        if (is_null($mtime)) {
439
+            $mtime = time();
440
+        }
441
+        $path = $this->cleanPath($path);
442
+
443
+        // if file exists, update the mtime, else create a new empty file
444
+        if ($this->file_exists($path)) {
445
+            try {
446
+                $this->statCache->remove($path);
447
+                $this->client->proppatch($this->encodePath($path), ['{DAV:}lastmodified' => $mtime]);
448
+                // non-owncloud clients might not have accepted the property, need to recheck it
449
+                $response = $this->client->propfind($this->encodePath($path), ['{DAV:}getlastmodified'], 0);
450
+                if ($response === false) {
451
+                    return false;
452
+                }
453
+                if (isset($response['{DAV:}getlastmodified'])) {
454
+                    $remoteMtime = strtotime($response['{DAV:}getlastmodified']);
455
+                    if ($remoteMtime !== $mtime) {
456
+                        // server has not accepted the mtime
457
+                        return false;
458
+                    }
459
+                }
460
+            } catch (ClientHttpException $e) {
461
+                if ($e->getHttpStatus() === 501) {
462
+                    return false;
463
+                }
464
+                $this->convertException($e, $path);
465
+                return false;
466
+            } catch (\Exception $e) {
467
+                $this->convertException($e, $path);
468
+                return false;
469
+            }
470
+        } else {
471
+            $this->file_put_contents($path, '');
472
+        }
473
+        return true;
474
+    }
475
+
476
+    /**
477
+     * @param string $path
478
+     * @param string $data
479
+     * @return int
480
+     */
481
+    public function file_put_contents($path, $data) {
482
+        $path = $this->cleanPath($path);
483
+        $result = parent::file_put_contents($path, $data);
484
+        $this->statCache->remove($path);
485
+        return $result;
486
+    }
487
+
488
+    /**
489
+     * @param string $path
490
+     * @param string $target
491
+     */
492
+    protected function uploadFile($path, $target) {
493
+        $this->init();
494
+
495
+        // invalidate
496
+        $target = $this->cleanPath($target);
497
+        $this->statCache->remove($target);
498
+        $source = fopen($path, 'r');
499
+
500
+        $this->httpClientService
501
+            ->newClient()
502
+            ->put($this->createBaseUri() . $this->encodePath($target), [
503
+                'body' => $source,
504
+                'auth' => [$this->user, $this->password]
505
+            ]);
506
+
507
+        $this->removeCachedFile($target);
508
+    }
509
+
510
+    /** {@inheritdoc} */
511
+    public function rename($path1, $path2) {
512
+        $this->init();
513
+        $path1 = $this->cleanPath($path1);
514
+        $path2 = $this->cleanPath($path2);
515
+        try {
516
+            // overwrite directory ?
517
+            if ($this->is_dir($path2)) {
518
+                // needs trailing slash in destination
519
+                $path2 = rtrim($path2, '/') . '/';
520
+            }
521
+            $this->client->request(
522
+                'MOVE',
523
+                $this->encodePath($path1),
524
+                null,
525
+                [
526
+                    'Destination' => $this->createBaseUri() . $this->encodePath($path2),
527
+                ]
528
+            );
529
+            $this->statCache->clear($path1 . '/');
530
+            $this->statCache->clear($path2 . '/');
531
+            $this->statCache->set($path1, false);
532
+            $this->statCache->set($path2, true);
533
+            $this->removeCachedFile($path1);
534
+            $this->removeCachedFile($path2);
535
+            return true;
536
+        } catch (\Exception $e) {
537
+            $this->convertException($e);
538
+        }
539
+        return false;
540
+    }
541
+
542
+    /** {@inheritdoc} */
543
+    public function copy($path1, $path2) {
544
+        $this->init();
545
+        $path1 = $this->cleanPath($path1);
546
+        $path2 = $this->cleanPath($path2);
547
+        try {
548
+            // overwrite directory ?
549
+            if ($this->is_dir($path2)) {
550
+                // needs trailing slash in destination
551
+                $path2 = rtrim($path2, '/') . '/';
552
+            }
553
+            $this->client->request(
554
+                'COPY',
555
+                $this->encodePath($path1),
556
+                null,
557
+                [
558
+                    'Destination' => $this->createBaseUri() . $this->encodePath($path2),
559
+                ]
560
+            );
561
+            $this->statCache->clear($path2 . '/');
562
+            $this->statCache->set($path2, true);
563
+            $this->removeCachedFile($path2);
564
+            return true;
565
+        } catch (\Exception $e) {
566
+            $this->convertException($e);
567
+        }
568
+        return false;
569
+    }
570
+
571
+    /** {@inheritdoc} */
572
+    public function stat($path) {
573
+        try {
574
+            $response = $this->propfind($path);
575
+            if (!$response) {
576
+                return false;
577
+            }
578
+            return [
579
+                'mtime' => strtotime($response['{DAV:}getlastmodified']),
580
+                'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0,
581
+            ];
582
+        } catch (\Exception $e) {
583
+            $this->convertException($e, $path);
584
+        }
585
+        return array();
586
+    }
587
+
588
+    /** {@inheritdoc} */
589
+    public function getMimeType($path) {
590
+        $remoteMimetype = $this->getMimeTypeFromRemote($path);
591
+        if ($remoteMimetype === 'application/octet-stream') {
592
+            return \OC::$server->getMimeTypeDetector()->detectPath($path);
593
+        } else {
594
+            return $remoteMimetype;
595
+        }
596
+    }
597
+
598
+    public function getMimeTypeFromRemote($path) {
599
+        try {
600
+            $response = $this->propfind($path);
601
+            if ($response === false) {
602
+                return false;
603
+            }
604
+            $responseType = [];
605
+            if (isset($response["{DAV:}resourcetype"])) {
606
+                /** @var ResourceType[] $response */
607
+                $responseType = $response["{DAV:}resourcetype"]->getValue();
608
+            }
609
+            $type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
610
+            if ($type == 'dir') {
611
+                return 'httpd/unix-directory';
612
+            } elseif (isset($response['{DAV:}getcontenttype'])) {
613
+                return $response['{DAV:}getcontenttype'];
614
+            } else {
615
+                return 'application/octet-stream';
616
+            }
617
+        } catch (\Exception $e) {
618
+            return false;
619
+        }
620
+    }
621
+
622
+    /**
623
+     * @param string $path
624
+     * @return string
625
+     */
626
+    public function cleanPath($path) {
627
+        if ($path === '') {
628
+            return $path;
629
+        }
630
+        $path = Filesystem::normalizePath($path);
631
+        // remove leading slash
632
+        return substr($path, 1);
633
+    }
634
+
635
+    /**
636
+     * URL encodes the given path but keeps the slashes
637
+     *
638
+     * @param string $path to encode
639
+     * @return string encoded path
640
+     */
641
+    protected function encodePath($path) {
642
+        // slashes need to stay
643
+        return str_replace('%2F', '/', rawurlencode($path));
644
+    }
645
+
646
+    /**
647
+     * @param string $method
648
+     * @param string $path
649
+     * @param string|resource|null $body
650
+     * @param int $expected
651
+     * @return bool
652
+     * @throws StorageInvalidException
653
+     * @throws StorageNotAvailableException
654
+     */
655
+    protected function simpleResponse($method, $path, $body, $expected) {
656
+        $path = $this->cleanPath($path);
657
+        try {
658
+            $response = $this->client->request($method, $this->encodePath($path), $body);
659
+            return $response['statusCode'] == $expected;
660
+        } catch (ClientHttpException $e) {
661
+            if ($e->getHttpStatus() === 404 && $method === 'DELETE') {
662
+                $this->statCache->clear($path . '/');
663
+                $this->statCache->set($path, false);
664
+                return false;
665
+            }
666
+
667
+            $this->convertException($e, $path);
668
+        } catch (\Exception $e) {
669
+            $this->convertException($e, $path);
670
+        }
671
+        return false;
672
+    }
673
+
674
+    /**
675
+     * check if curl is installed
676
+     */
677
+    public static function checkDependencies() {
678
+        return true;
679
+    }
680
+
681
+    /** {@inheritdoc} */
682
+    public function isUpdatable($path) {
683
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
684
+    }
685
+
686
+    /** {@inheritdoc} */
687
+    public function isCreatable($path) {
688
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
689
+    }
690
+
691
+    /** {@inheritdoc} */
692
+    public function isSharable($path) {
693
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
694
+    }
695
+
696
+    /** {@inheritdoc} */
697
+    public function isDeletable($path) {
698
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
699
+    }
700
+
701
+    /** {@inheritdoc} */
702
+    public function getPermissions($path) {
703
+        $this->init();
704
+        $path = $this->cleanPath($path);
705
+        $response = $this->propfind($path);
706
+        if ($response === false) {
707
+            return 0;
708
+        }
709
+        if (isset($response['{http://owncloud.org/ns}permissions'])) {
710
+            return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
711
+        } else if ($this->is_dir($path)) {
712
+            return Constants::PERMISSION_ALL;
713
+        } else if ($this->file_exists($path)) {
714
+            return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
715
+        } else {
716
+            return 0;
717
+        }
718
+    }
719
+
720
+    /** {@inheritdoc} */
721
+    public function getETag($path) {
722
+        $this->init();
723
+        $path = $this->cleanPath($path);
724
+        $response = $this->propfind($path);
725
+        if ($response === false) {
726
+            return null;
727
+        }
728
+        if (isset($response['{DAV:}getetag'])) {
729
+            $etag = trim($response['{DAV:}getetag'], '"');
730
+            if (strlen($etag) > 40) {
731
+                $etag = md5($etag);
732
+            }
733
+            return $etag;
734
+        }
735
+        return parent::getEtag($path);
736
+    }
737
+
738
+    /**
739
+     * @param string $permissionsString
740
+     * @return int
741
+     */
742
+    protected function parsePermissions($permissionsString) {
743
+        $permissions = Constants::PERMISSION_READ;
744
+        if (strpos($permissionsString, 'R') !== false) {
745
+            $permissions |= Constants::PERMISSION_SHARE;
746
+        }
747
+        if (strpos($permissionsString, 'D') !== false) {
748
+            $permissions |= Constants::PERMISSION_DELETE;
749
+        }
750
+        if (strpos($permissionsString, 'W') !== false) {
751
+            $permissions |= Constants::PERMISSION_UPDATE;
752
+        }
753
+        if (strpos($permissionsString, 'CK') !== false) {
754
+            $permissions |= Constants::PERMISSION_CREATE;
755
+            $permissions |= Constants::PERMISSION_UPDATE;
756
+        }
757
+        return $permissions;
758
+    }
759
+
760
+    /**
761
+     * check if a file or folder has been updated since $time
762
+     *
763
+     * @param string $path
764
+     * @param int $time
765
+     * @throws \OCP\Files\StorageNotAvailableException
766
+     * @return bool
767
+     */
768
+    public function hasUpdated($path, $time) {
769
+        $this->init();
770
+        $path = $this->cleanPath($path);
771
+        try {
772
+            // force refresh for $path
773
+            $this->statCache->remove($path);
774
+            $response = $this->propfind($path);
775
+            if ($response === false) {
776
+                if ($path === '') {
777
+                    // if root is gone it means the storage is not available
778
+                    throw new StorageNotAvailableException('root is gone');
779
+                }
780
+                return false;
781
+            }
782
+            if (isset($response['{DAV:}getetag'])) {
783
+                $cachedData = $this->getCache()->get($path);
784
+                $etag = null;
785
+                if (isset($response['{DAV:}getetag'])) {
786
+                    $etag = trim($response['{DAV:}getetag'], '"');
787
+                }
788
+                if (!empty($etag) && $cachedData['etag'] !== $etag) {
789
+                    return true;
790
+                } else if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
791
+                    $sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions'];
792
+                    return $sharePermissions !== $cachedData['permissions'];
793
+                } else if (isset($response['{http://owncloud.org/ns}permissions'])) {
794
+                    $permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
795
+                    return $permissions !== $cachedData['permissions'];
796
+                } else {
797
+                    return false;
798
+                }
799
+            } else {
800
+                $remoteMtime = strtotime($response['{DAV:}getlastmodified']);
801
+                return $remoteMtime > $time;
802
+            }
803
+        } catch (ClientHttpException $e) {
804
+            if ($e->getHttpStatus() === 405) {
805
+                if ($path === '') {
806
+                    // if root is gone it means the storage is not available
807
+                    throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
808
+                }
809
+                return false;
810
+            }
811
+            $this->convertException($e, $path);
812
+            return false;
813
+        } catch (\Exception $e) {
814
+            $this->convertException($e, $path);
815
+            return false;
816
+        }
817
+    }
818
+
819
+    /**
820
+     * Interpret the given exception and decide whether it is due to an
821
+     * unavailable storage, invalid storage or other.
822
+     * This will either throw StorageInvalidException, StorageNotAvailableException
823
+     * or do nothing.
824
+     *
825
+     * @param Exception $e sabre exception
826
+     * @param string $path optional path from the operation
827
+     *
828
+     * @throws StorageInvalidException if the storage is invalid, for example
829
+     * when the authentication expired or is invalid
830
+     * @throws StorageNotAvailableException if the storage is not available,
831
+     * which might be temporary
832
+     */
833
+    protected function convertException(Exception $e, $path = '') {
834
+        \OC::$server->getLogger()->logException($e, ['app' => 'files_external', 'level' => ILogger::DEBUG]);
835
+        if ($e instanceof ClientHttpException) {
836
+            if ($e->getHttpStatus() === Http::STATUS_LOCKED) {
837
+                throw new \OCP\Lock\LockedException($path);
838
+            }
839
+            if ($e->getHttpStatus() === Http::STATUS_UNAUTHORIZED) {
840
+                // either password was changed or was invalid all along
841
+                throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage());
842
+            } else if ($e->getHttpStatus() === Http::STATUS_METHOD_NOT_ALLOWED) {
843
+                // ignore exception for MethodNotAllowed, false will be returned
844
+                return;
845
+            }
846
+            throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
847
+        } else if ($e instanceof ClientException) {
848
+            // connection timeout or refused, server could be temporarily down
849
+            throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
850
+        } else if ($e instanceof \InvalidArgumentException) {
851
+            // parse error because the server returned HTML instead of XML,
852
+            // possibly temporarily down
853
+            throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
854
+        } else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
855
+            // rethrow
856
+            throw $e;
857
+        }
858
+
859
+        // TODO: only log for now, but in the future need to wrap/rethrow exception
860
+    }
861 861
 }
862 862
 
Please login to merge, or discard this patch.
Spacing   +33 added lines, -33 removed lines patch added patch discarded remove patch
@@ -103,7 +103,7 @@  discard block
 block discarded – undo
103 103
 				if (is_string($params['secure'])) {
104 104
 					$this->secure = ($params['secure'] === 'true');
105 105
 				} else {
106
-					$this->secure = (bool)$params['secure'];
106
+					$this->secure = (bool) $params['secure'];
107 107
 				}
108 108
 			} else {
109 109
 				$this->secure = false;
@@ -120,8 +120,8 @@  discard block
 block discarded – undo
120 120
 				}
121 121
 			}
122 122
 			$this->root = $params['root'] ?? '/';
123
-			$this->root = '/' . ltrim($this->root, '/');
124
-			$this->root = rtrim($this->root, '/') . '/';
123
+			$this->root = '/'.ltrim($this->root, '/');
124
+			$this->root = rtrim($this->root, '/').'/';
125 125
 		} else {
126 126
 			throw new \Exception('Invalid webdav storage configuration');
127 127
 		}
@@ -163,7 +163,7 @@  discard block
 block discarded – undo
163 163
 
164 164
 	/** {@inheritdoc} */
165 165
 	public function getId() {
166
-		return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
166
+		return 'webdav::'.$this->user.'@'.$this->host.'/'.$this->root;
167 167
 	}
168 168
 
169 169
 	/** {@inheritdoc} */
@@ -172,7 +172,7 @@  discard block
 block discarded – undo
172 172
 		if ($this->secure) {
173 173
 			$baseUri .= 's';
174 174
 		}
175
-		$baseUri .= '://' . $this->host . $this->root;
175
+		$baseUri .= '://'.$this->host.$this->root;
176 176
 		return $baseUri;
177 177
 	}
178 178
 
@@ -193,8 +193,8 @@  discard block
 block discarded – undo
193 193
 		$path = $this->cleanPath($path);
194 194
 		// FIXME: some WebDAV impl return 403 when trying to DELETE
195 195
 		// a non-empty folder
196
-		$result = $this->simpleResponse('DELETE', $path . '/', null, 204);
197
-		$this->statCache->clear($path . '/');
196
+		$result = $this->simpleResponse('DELETE', $path.'/', null, 204);
197
+		$this->statCache->clear($path.'/');
198 198
 		$this->statCache->remove($path);
199 199
 		return $result;
200 200
 	}
@@ -269,7 +269,7 @@  discard block
 block discarded – undo
269 269
 				$this->statCache->set($path, $response);
270 270
 			} catch (ClientHttpException $e) {
271 271
 				if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) {
272
-					$this->statCache->clear($path . '/');
272
+					$this->statCache->clear($path.'/');
273 273
 					$this->statCache->set($path, false);
274 274
 					return false;
275 275
 				}
@@ -326,7 +326,7 @@  discard block
 block discarded – undo
326 326
 		$this->init();
327 327
 		$path = $this->cleanPath($path);
328 328
 		$result = $this->simpleResponse('DELETE', $path, null, 204);
329
-		$this->statCache->clear($path . '/');
329
+		$this->statCache->clear($path.'/');
330 330
 		$this->statCache->remove($path);
331 331
 		return $result;
332 332
 	}
@@ -341,7 +341,7 @@  discard block
 block discarded – undo
341 341
 				try {
342 342
 					$response = $this->httpClientService
343 343
 						->newClient()
344
-						->get($this->createBaseUri() . $this->encodePath($path), [
344
+						->get($this->createBaseUri().$this->encodePath($path), [
345 345
 							'auth' => [$this->user, $this->password],
346 346
 							'stream' => true
347 347
 						]);
@@ -358,7 +358,7 @@  discard block
 block discarded – undo
358 358
 					if ($response->getStatusCode() === Http::STATUS_LOCKED) {
359 359
 						throw new \OCP\Lock\LockedException($path);
360 360
 					} else {
361
-						Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), ILogger::ERROR);
361
+						Util::writeLog("webdav client", 'Guzzle get returned status code '.$response->getStatusCode(), ILogger::ERROR);
362 362
 					}
363 363
 				}
364 364
 
@@ -398,7 +398,7 @@  discard block
 block discarded – undo
398 398
 					$tmpFile = $tempManager->getTemporaryFile($ext);
399 399
 				}
400 400
 				$handle = fopen($tmpFile, $mode);
401
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
401
+				return CallbackWrapper::wrap($handle, null, null, function() use ($path, $tmpFile) {
402 402
 					$this->writeBack($tmpFile, $path);
403 403
 				});
404 404
 		}
@@ -423,7 +423,7 @@  discard block
 block discarded – undo
423 423
 				return FileInfo::SPACE_UNKNOWN;
424 424
 			}
425 425
 			if (isset($response['{DAV:}quota-available-bytes'])) {
426
-				return (int)$response['{DAV:}quota-available-bytes'];
426
+				return (int) $response['{DAV:}quota-available-bytes'];
427 427
 			} else {
428 428
 				return FileInfo::SPACE_UNKNOWN;
429 429
 			}
@@ -499,7 +499,7 @@  discard block
 block discarded – undo
499 499
 
500 500
 		$this->httpClientService
501 501
 			->newClient()
502
-			->put($this->createBaseUri() . $this->encodePath($target), [
502
+			->put($this->createBaseUri().$this->encodePath($target), [
503 503
 				'body' => $source,
504 504
 				'auth' => [$this->user, $this->password]
505 505
 			]);
@@ -516,18 +516,18 @@  discard block
 block discarded – undo
516 516
 			// overwrite directory ?
517 517
 			if ($this->is_dir($path2)) {
518 518
 				// needs trailing slash in destination
519
-				$path2 = rtrim($path2, '/') . '/';
519
+				$path2 = rtrim($path2, '/').'/';
520 520
 			}
521 521
 			$this->client->request(
522 522
 				'MOVE',
523 523
 				$this->encodePath($path1),
524 524
 				null,
525 525
 				[
526
-					'Destination' => $this->createBaseUri() . $this->encodePath($path2),
526
+					'Destination' => $this->createBaseUri().$this->encodePath($path2),
527 527
 				]
528 528
 			);
529
-			$this->statCache->clear($path1 . '/');
530
-			$this->statCache->clear($path2 . '/');
529
+			$this->statCache->clear($path1.'/');
530
+			$this->statCache->clear($path2.'/');
531 531
 			$this->statCache->set($path1, false);
532 532
 			$this->statCache->set($path2, true);
533 533
 			$this->removeCachedFile($path1);
@@ -548,17 +548,17 @@  discard block
 block discarded – undo
548 548
 			// overwrite directory ?
549 549
 			if ($this->is_dir($path2)) {
550 550
 				// needs trailing slash in destination
551
-				$path2 = rtrim($path2, '/') . '/';
551
+				$path2 = rtrim($path2, '/').'/';
552 552
 			}
553 553
 			$this->client->request(
554 554
 				'COPY',
555 555
 				$this->encodePath($path1),
556 556
 				null,
557 557
 				[
558
-					'Destination' => $this->createBaseUri() . $this->encodePath($path2),
558
+					'Destination' => $this->createBaseUri().$this->encodePath($path2),
559 559
 				]
560 560
 			);
561
-			$this->statCache->clear($path2 . '/');
561
+			$this->statCache->clear($path2.'/');
562 562
 			$this->statCache->set($path2, true);
563 563
 			$this->removeCachedFile($path2);
564 564
 			return true;
@@ -577,7 +577,7 @@  discard block
 block discarded – undo
577 577
 			}
578 578
 			return [
579 579
 				'mtime' => strtotime($response['{DAV:}getlastmodified']),
580
-				'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0,
580
+				'size' => (int) isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0,
581 581
 			];
582 582
 		} catch (\Exception $e) {
583 583
 			$this->convertException($e, $path);
@@ -659,7 +659,7 @@  discard block
 block discarded – undo
659 659
 			return $response['statusCode'] == $expected;
660 660
 		} catch (ClientHttpException $e) {
661 661
 			if ($e->getHttpStatus() === 404 && $method === 'DELETE') {
662
-				$this->statCache->clear($path . '/');
662
+				$this->statCache->clear($path.'/');
663 663
 				$this->statCache->set($path, false);
664 664
 				return false;
665 665
 			}
@@ -680,22 +680,22 @@  discard block
 block discarded – undo
680 680
 
681 681
 	/** {@inheritdoc} */
682 682
 	public function isUpdatable($path) {
683
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
683
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
684 684
 	}
685 685
 
686 686
 	/** {@inheritdoc} */
687 687
 	public function isCreatable($path) {
688
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
688
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_CREATE);
689 689
 	}
690 690
 
691 691
 	/** {@inheritdoc} */
692 692
 	public function isSharable($path) {
693
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
693
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_SHARE);
694 694
 	}
695 695
 
696 696
 	/** {@inheritdoc} */
697 697
 	public function isDeletable($path) {
698
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
698
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_DELETE);
699 699
 	}
700 700
 
701 701
 	/** {@inheritdoc} */
@@ -788,7 +788,7 @@  discard block
 block discarded – undo
788 788
 				if (!empty($etag) && $cachedData['etag'] !== $etag) {
789 789
 					return true;
790 790
 				} else if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
791
-					$sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions'];
791
+					$sharePermissions = (int) $response['{http://open-collaboration-services.org/ns}share-permissions'];
792 792
 					return $sharePermissions !== $cachedData['permissions'];
793 793
 				} else if (isset($response['{http://owncloud.org/ns}permissions'])) {
794 794
 					$permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
@@ -804,7 +804,7 @@  discard block
 block discarded – undo
804 804
 			if ($e->getHttpStatus() === 405) {
805 805
 				if ($path === '') {
806 806
 					// if root is gone it means the storage is not available
807
-					throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
807
+					throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
808 808
 				}
809 809
 				return false;
810 810
 			}
@@ -838,19 +838,19 @@  discard block
 block discarded – undo
838 838
 			}
839 839
 			if ($e->getHttpStatus() === Http::STATUS_UNAUTHORIZED) {
840 840
 				// either password was changed or was invalid all along
841
-				throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage());
841
+				throw new StorageInvalidException(get_class($e).': '.$e->getMessage());
842 842
 			} else if ($e->getHttpStatus() === Http::STATUS_METHOD_NOT_ALLOWED) {
843 843
 				// ignore exception for MethodNotAllowed, false will be returned
844 844
 				return;
845 845
 			}
846
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
846
+			throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
847 847
 		} else if ($e instanceof ClientException) {
848 848
 			// connection timeout or refused, server could be temporarily down
849
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
849
+			throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
850 850
 		} else if ($e instanceof \InvalidArgumentException) {
851 851
 			// parse error because the server returned HTML instead of XML,
852 852
 			// possibly temporarily down
853
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
853
+			throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
854 854
 		} else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
855 855
 			// rethrow
856 856
 			throw $e;
Please login to merge, or discard this patch.
lib/private/Files/Storage/Temporary.php 2 patches
Indentation   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -29,20 +29,20 @@
 block discarded – undo
29 29
  * local storage backend in temporary folder for testing purpose
30 30
  */
31 31
 class Temporary extends Local{
32
-	public function __construct($arguments = null) {
33
-		parent::__construct(array('datadir' => \OC::$server->getTempManager()->getTemporaryFolder()));
34
-	}
32
+    public function __construct($arguments = null) {
33
+        parent::__construct(array('datadir' => \OC::$server->getTempManager()->getTemporaryFolder()));
34
+    }
35 35
 
36
-	public function cleanUp() {
37
-		\OC_Helper::rmdirr($this->datadir);
38
-	}
36
+    public function cleanUp() {
37
+        \OC_Helper::rmdirr($this->datadir);
38
+    }
39 39
 
40
-	public function __destruct() {
41
-		parent::__destruct();
42
-		$this->cleanUp();
43
-	}
40
+    public function __destruct() {
41
+        parent::__destruct();
42
+        $this->cleanUp();
43
+    }
44 44
 
45
-	public function getDataDir() {
46
-		return $this->datadir;
47
-	}
45
+    public function getDataDir() {
46
+        return $this->datadir;
47
+    }
48 48
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -28,7 +28,7 @@
 block discarded – undo
28 28
 /**
29 29
  * local storage backend in temporary folder for testing purpose
30 30
  */
31
-class Temporary extends Local{
31
+class Temporary extends Local {
32 32
 	public function __construct($arguments = null) {
33 33
 		parent::__construct(array('datadir' => \OC::$server->getTempManager()->getTemporaryFolder()));
34 34
 	}
Please login to merge, or discard this patch.
lib/private/Files/Storage/Local.php 3 patches
Braces   +3 added lines, -2 removed lines patch added patch discarded remove patch
@@ -317,8 +317,9 @@
 block discarded – undo
317 317
 		$files = array();
318 318
 		$physicalDir = $this->getSourcePath($dir);
319 319
 		foreach (scandir($physicalDir) as $item) {
320
-			if (\OC\Files\Filesystem::isIgnoredDir($item))
321
-				continue;
320
+			if (\OC\Files\Filesystem::isIgnoredDir($item)) {
321
+							continue;
322
+			}
322 323
 			$physicalItem = $physicalDir . '/' . $item;
323 324
 
324 325
 			if (strstr(strtolower($item), strtolower($query)) !== false) {
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -67,7 +67,7 @@  discard block
 block discarded – undo
67 67
 			$this->realDataDir = $this->datadir;
68 68
 		} else {
69 69
 			$realPath = realpath($this->datadir) ?: $this->datadir;
70
-			$this->realDataDir = rtrim($realPath, '/') . '/';
70
+			$this->realDataDir = rtrim($realPath, '/').'/';
71 71
 		}
72 72
 		if (substr($this->datadir, -1) !== '/') {
73 73
 			$this->datadir .= '/';
@@ -79,7 +79,7 @@  discard block
 block discarded – undo
79 79
 	}
80 80
 
81 81
 	public function getId() {
82
-		return 'local::' . $this->datadir;
82
+		return 'local::'.$this->datadir;
83 83
 	}
84 84
 
85 85
 	public function mkdir($path) {
@@ -249,17 +249,17 @@  discard block
 block discarded – undo
249 249
 		$dstParent = dirname($path2);
250 250
 
251 251
 		if (!$this->isUpdatable($srcParent)) {
252
-			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
252
+			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : '.$srcParent, ILogger::ERROR);
253 253
 			return false;
254 254
 		}
255 255
 
256 256
 		if (!$this->isUpdatable($dstParent)) {
257
-			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
257
+			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : '.$dstParent, ILogger::ERROR);
258 258
 			return false;
259 259
 		}
260 260
 
261 261
 		if (!$this->file_exists($path1)) {
262
-			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
262
+			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : '.$path1, ILogger::ERROR);
263 263
 			return false;
264 264
 		}
265 265
 
@@ -344,13 +344,13 @@  discard block
 block discarded – undo
344 344
 		foreach (scandir($physicalDir) as $item) {
345 345
 			if (\OC\Files\Filesystem::isIgnoredDir($item))
346 346
 				continue;
347
-			$physicalItem = $physicalDir . '/' . $item;
347
+			$physicalItem = $physicalDir.'/'.$item;
348 348
 
349 349
 			if (strstr(strtolower($item), strtolower($query)) !== false) {
350
-				$files[] = $dir . '/' . $item;
350
+				$files[] = $dir.'/'.$item;
351 351
 			}
352 352
 			if (is_dir($physicalItem)) {
353
-				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
353
+				$files = array_merge($files, $this->searchInDir($query, $dir.'/'.$item));
354 354
 			}
355 355
 		}
356 356
 		return $files;
@@ -383,7 +383,7 @@  discard block
 block discarded – undo
383 383
 			throw new ForbiddenException('Invalid path', false);
384 384
 		}
385 385
 
386
-		$fullPath = $this->datadir . $path;
386
+		$fullPath = $this->datadir.$path;
387 387
 		$currentPath = $path;
388 388
 		if ($this->allowSymlinks || $currentPath === '') {
389 389
 			return $fullPath;
@@ -395,10 +395,10 @@  discard block
 block discarded – undo
395 395
 			if ($currentPath === '' || $currentPath === '.') {
396 396
 				return $fullPath;
397 397
 			}
398
-			$realPath = realpath($this->datadir . $currentPath);
398
+			$realPath = realpath($this->datadir.$currentPath);
399 399
 		}
400 400
 		if ($realPath) {
401
-			$realPath = $realPath . '/';
401
+			$realPath = $realPath.'/';
402 402
 		}
403 403
 		if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
404 404
 			return $fullPath;
@@ -425,9 +425,9 @@  discard block
 block discarded – undo
425 425
 		if ($this->is_file($path)) {
426 426
 			$stat = $this->stat($path);
427 427
 			return md5(
428
-				$stat['mtime'] .
429
-				$stat['ino'] .
430
-				$stat['dev'] .
428
+				$stat['mtime'].
429
+				$stat['ino'].
430
+				$stat['dev'].
431 431
 				$stat['size']
432 432
 			);
433 433
 		} else {
@@ -485,6 +485,6 @@  discard block
 block discarded – undo
485 485
 	}
486 486
 
487 487
 	public function writeStream(string $path, $stream, int $size = null): int {
488
-		return (int)file_put_contents($this->getSourcePath($path), $stream);
488
+		return (int) file_put_contents($this->getSourcePath($path), $stream);
489 489
 	}
490 490
 }
Please login to merge, or discard this patch.
Indentation   +438 added lines, -438 removed lines patch added patch discarded remove patch
@@ -49,442 +49,442 @@
 block discarded – undo
49 49
  * for local filestore, we only have to map the paths
50 50
  */
51 51
 class Local extends \OC\Files\Storage\Common {
52
-	protected $datadir;
53
-
54
-	protected $dataDirLength;
55
-
56
-	protected $allowSymlinks = false;
57
-
58
-	protected $realDataDir;
59
-
60
-	public function __construct($arguments) {
61
-		if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
62
-			throw new \InvalidArgumentException('No data directory set for local storage');
63
-		}
64
-		$this->datadir = str_replace('//', '/', $arguments['datadir']);
65
-		// some crazy code uses a local storage on root...
66
-		if ($this->datadir === '/') {
67
-			$this->realDataDir = $this->datadir;
68
-		} else {
69
-			$realPath = realpath($this->datadir) ?: $this->datadir;
70
-			$this->realDataDir = rtrim($realPath, '/') . '/';
71
-		}
72
-		if (substr($this->datadir, -1) !== '/') {
73
-			$this->datadir .= '/';
74
-		}
75
-		$this->dataDirLength = strlen($this->realDataDir);
76
-	}
77
-
78
-	public function __destruct() {
79
-	}
80
-
81
-	public function getId() {
82
-		return 'local::' . $this->datadir;
83
-	}
84
-
85
-	public function mkdir($path) {
86
-		return @mkdir($this->getSourcePath($path), 0777, true);
87
-	}
88
-
89
-	public function rmdir($path) {
90
-		if (!$this->isDeletable($path)) {
91
-			return false;
92
-		}
93
-		try {
94
-			$it = new \RecursiveIteratorIterator(
95
-				new \RecursiveDirectoryIterator($this->getSourcePath($path)),
96
-				\RecursiveIteratorIterator::CHILD_FIRST
97
-			);
98
-			/**
99
-			 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
100
-			 * This bug is fixed in PHP 5.5.9 or before
101
-			 * See #8376
102
-			 */
103
-			$it->rewind();
104
-			while ($it->valid()) {
105
-				/**
106
-				 * @var \SplFileInfo $file
107
-				 */
108
-				$file = $it->current();
109
-				if (in_array($file->getBasename(), array('.', '..'))) {
110
-					$it->next();
111
-					continue;
112
-				} elseif ($file->isDir()) {
113
-					rmdir($file->getPathname());
114
-				} elseif ($file->isFile() || $file->isLink()) {
115
-					unlink($file->getPathname());
116
-				}
117
-				$it->next();
118
-			}
119
-			return rmdir($this->getSourcePath($path));
120
-		} catch (\UnexpectedValueException $e) {
121
-			return false;
122
-		}
123
-	}
124
-
125
-	public function opendir($path) {
126
-		return opendir($this->getSourcePath($path));
127
-	}
128
-
129
-	public function is_dir($path) {
130
-		if (substr($path, -1) == '/') {
131
-			$path = substr($path, 0, -1);
132
-		}
133
-		return is_dir($this->getSourcePath($path));
134
-	}
135
-
136
-	public function is_file($path) {
137
-		return is_file($this->getSourcePath($path));
138
-	}
139
-
140
-	public function stat($path) {
141
-		clearstatcache();
142
-		$fullPath = $this->getSourcePath($path);
143
-		$statResult = stat($fullPath);
144
-		if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) {
145
-			$filesize = $this->filesize($path);
146
-			$statResult['size'] = $filesize;
147
-			$statResult[7] = $filesize;
148
-		}
149
-		return $statResult;
150
-	}
151
-
152
-	public function filetype($path) {
153
-		$filetype = filetype($this->getSourcePath($path));
154
-		if ($filetype == 'link') {
155
-			$filetype = filetype(realpath($this->getSourcePath($path)));
156
-		}
157
-		return $filetype;
158
-	}
159
-
160
-	public function filesize($path) {
161
-		if ($this->is_dir($path)) {
162
-			return 0;
163
-		}
164
-		$fullPath = $this->getSourcePath($path);
165
-		if (PHP_INT_SIZE === 4) {
166
-			$helper = new \OC\LargeFileHelper;
167
-			return $helper->getFileSize($fullPath);
168
-		}
169
-		return filesize($fullPath);
170
-	}
171
-
172
-	public function isReadable($path) {
173
-		return is_readable($this->getSourcePath($path));
174
-	}
175
-
176
-	public function isUpdatable($path) {
177
-		return is_writable($this->getSourcePath($path));
178
-	}
179
-
180
-	public function file_exists($path) {
181
-		return file_exists($this->getSourcePath($path));
182
-	}
183
-
184
-	public function filemtime($path) {
185
-		$fullPath = $this->getSourcePath($path);
186
-		clearstatcache(true, $fullPath);
187
-		if (!$this->file_exists($path)) {
188
-			return false;
189
-		}
190
-		if (PHP_INT_SIZE === 4) {
191
-			$helper = new \OC\LargeFileHelper();
192
-			return $helper->getFileMtime($fullPath);
193
-		}
194
-		return filemtime($fullPath);
195
-	}
196
-
197
-	public function touch($path, $mtime = null) {
198
-		// sets the modification time of the file to the given value.
199
-		// If mtime is nil the current time is set.
200
-		// note that the access time of the file always changes to the current time.
201
-		if ($this->file_exists($path) and !$this->isUpdatable($path)) {
202
-			return false;
203
-		}
204
-		if (!is_null($mtime)) {
205
-			$result = @touch($this->getSourcePath($path), $mtime);
206
-		} else {
207
-			$result = @touch($this->getSourcePath($path));
208
-		}
209
-		if ($result) {
210
-			clearstatcache(true, $this->getSourcePath($path));
211
-		}
212
-
213
-		return $result;
214
-	}
215
-
216
-	public function file_get_contents($path) {
217
-		return file_get_contents($this->getSourcePath($path));
218
-	}
219
-
220
-	public function file_put_contents($path, $data) {
221
-		return file_put_contents($this->getSourcePath($path), $data);
222
-	}
223
-
224
-	public function unlink($path) {
225
-		if ($this->is_dir($path)) {
226
-			return $this->rmdir($path);
227
-		} else if ($this->is_file($path)) {
228
-			return unlink($this->getSourcePath($path));
229
-		} else {
230
-			return false;
231
-		}
232
-
233
-	}
234
-
235
-	private function treeContainsBlacklistedFile(string $path): bool {
236
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
237
-		foreach ($iterator as $file) {
238
-			/** @var \SplFileInfo $file */
239
-			if (Filesystem::isFileBlacklisted($file->getBasename())) {
240
-				return true;
241
-			}
242
-		}
243
-
244
-		return false;
245
-	}
246
-
247
-	public function rename($path1, $path2) {
248
-		$srcParent = dirname($path1);
249
-		$dstParent = dirname($path2);
250
-
251
-		if (!$this->isUpdatable($srcParent)) {
252
-			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
253
-			return false;
254
-		}
255
-
256
-		if (!$this->isUpdatable($dstParent)) {
257
-			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
258
-			return false;
259
-		}
260
-
261
-		if (!$this->file_exists($path1)) {
262
-			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
263
-			return false;
264
-		}
265
-
266
-		if ($this->is_dir($path2)) {
267
-			$this->rmdir($path2);
268
-		} else if ($this->is_file($path2)) {
269
-			$this->unlink($path2);
270
-		}
271
-
272
-		if ($this->is_dir($path1)) {
273
-			// we can't move folders across devices, use copy instead
274
-			$stat1 = stat(dirname($this->getSourcePath($path1)));
275
-			$stat2 = stat(dirname($this->getSourcePath($path2)));
276
-			if ($stat1['dev'] !== $stat2['dev']) {
277
-				$result = $this->copy($path1, $path2);
278
-				if ($result) {
279
-					$result &= $this->rmdir($path1);
280
-				}
281
-				return $result;
282
-			}
283
-
284
-			if ($this->treeContainsBlacklistedFile($this->getSourcePath($path1))) {
285
-				throw new ForbiddenException('Invalid path', false);
286
-			}
287
-		}
288
-
289
-		return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
290
-	}
291
-
292
-	public function copy($path1, $path2) {
293
-		if ($this->is_dir($path1)) {
294
-			return parent::copy($path1, $path2);
295
-		} else {
296
-			return copy($this->getSourcePath($path1), $this->getSourcePath($path2));
297
-		}
298
-	}
299
-
300
-	public function fopen($path, $mode) {
301
-		return fopen($this->getSourcePath($path), $mode);
302
-	}
303
-
304
-	public function hash($type, $path, $raw = false) {
305
-		return hash_file($type, $this->getSourcePath($path), $raw);
306
-	}
307
-
308
-	public function free_space($path) {
309
-		$sourcePath = $this->getSourcePath($path);
310
-		// using !is_dir because $sourcePath might be a part file or
311
-		// non-existing file, so we'd still want to use the parent dir
312
-		// in such cases
313
-		if (!is_dir($sourcePath)) {
314
-			// disk_free_space doesn't work on files
315
-			$sourcePath = dirname($sourcePath);
316
-		}
317
-		$space = @disk_free_space($sourcePath);
318
-		if ($space === false || is_null($space)) {
319
-			return \OCP\Files\FileInfo::SPACE_UNKNOWN;
320
-		}
321
-		return $space;
322
-	}
323
-
324
-	public function search($query) {
325
-		return $this->searchInDir($query);
326
-	}
327
-
328
-	public function getLocalFile($path) {
329
-		return $this->getSourcePath($path);
330
-	}
331
-
332
-	public function getLocalFolder($path) {
333
-		return $this->getSourcePath($path);
334
-	}
335
-
336
-	/**
337
-	 * @param string $query
338
-	 * @param string $dir
339
-	 * @return array
340
-	 */
341
-	protected function searchInDir($query, $dir = '') {
342
-		$files = array();
343
-		$physicalDir = $this->getSourcePath($dir);
344
-		foreach (scandir($physicalDir) as $item) {
345
-			if (\OC\Files\Filesystem::isIgnoredDir($item))
346
-				continue;
347
-			$physicalItem = $physicalDir . '/' . $item;
348
-
349
-			if (strstr(strtolower($item), strtolower($query)) !== false) {
350
-				$files[] = $dir . '/' . $item;
351
-			}
352
-			if (is_dir($physicalItem)) {
353
-				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
354
-			}
355
-		}
356
-		return $files;
357
-	}
358
-
359
-	/**
360
-	 * check if a file or folder has been updated since $time
361
-	 *
362
-	 * @param string $path
363
-	 * @param int $time
364
-	 * @return bool
365
-	 */
366
-	public function hasUpdated($path, $time) {
367
-		if ($this->file_exists($path)) {
368
-			return $this->filemtime($path) > $time;
369
-		} else {
370
-			return true;
371
-		}
372
-	}
373
-
374
-	/**
375
-	 * Get the source path (on disk) of a given path
376
-	 *
377
-	 * @param string $path
378
-	 * @return string
379
-	 * @throws ForbiddenException
380
-	 */
381
-	public function getSourcePath($path) {
382
-		if (Filesystem::isFileBlacklisted($path)) {
383
-			throw new ForbiddenException('Invalid path', false);
384
-		}
385
-
386
-		$fullPath = $this->datadir . $path;
387
-		$currentPath = $path;
388
-		if ($this->allowSymlinks || $currentPath === '') {
389
-			return $fullPath;
390
-		}
391
-		$pathToResolve = $fullPath;
392
-		$realPath = realpath($pathToResolve);
393
-		while ($realPath === false) { // for non existing files check the parent directory
394
-			$currentPath = dirname($currentPath);
395
-			if ($currentPath === '' || $currentPath === '.') {
396
-				return $fullPath;
397
-			}
398
-			$realPath = realpath($this->datadir . $currentPath);
399
-		}
400
-		if ($realPath) {
401
-			$realPath = $realPath . '/';
402
-		}
403
-		if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
404
-			return $fullPath;
405
-		}
406
-
407
-		\OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ILogger::ERROR);
408
-		throw new ForbiddenException('Following symlinks is not allowed', false);
409
-	}
410
-
411
-	/**
412
-	 * {@inheritdoc}
413
-	 */
414
-	public function isLocal() {
415
-		return true;
416
-	}
417
-
418
-	/**
419
-	 * get the ETag for a file or folder
420
-	 *
421
-	 * @param string $path
422
-	 * @return string
423
-	 */
424
-	public function getETag($path) {
425
-		if ($this->is_file($path)) {
426
-			$stat = $this->stat($path);
427
-			return md5(
428
-				$stat['mtime'] .
429
-				$stat['ino'] .
430
-				$stat['dev'] .
431
-				$stat['size']
432
-			);
433
-		} else {
434
-			return parent::getETag($path);
435
-		}
436
-	}
437
-
438
-	/**
439
-	 * @param IStorage $sourceStorage
440
-	 * @param string $sourceInternalPath
441
-	 * @param string $targetInternalPath
442
-	 * @param bool $preserveMtime
443
-	 * @return bool
444
-	 */
445
-	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
446
-		if ($sourceStorage->instanceOfStorage(Local::class)) {
447
-			if ($sourceStorage->instanceOfStorage(Jail::class)) {
448
-				/**
449
-				 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
450
-				 */
451
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
452
-			}
453
-			/**
454
-			 * @var \OC\Files\Storage\Local $sourceStorage
455
-			 */
456
-			$rootStorage = new Local(['datadir' => '/']);
457
-			return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
458
-		} else {
459
-			return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
460
-		}
461
-	}
462
-
463
-	/**
464
-	 * @param IStorage $sourceStorage
465
-	 * @param string $sourceInternalPath
466
-	 * @param string $targetInternalPath
467
-	 * @return bool
468
-	 */
469
-	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
470
-		if ($sourceStorage->instanceOfStorage(Local::class)) {
471
-			if ($sourceStorage->instanceOfStorage(Jail::class)) {
472
-				/**
473
-				 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
474
-				 */
475
-				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
476
-			}
477
-			/**
478
-			 * @var \OC\Files\Storage\Local $sourceStorage
479
-			 */
480
-			$rootStorage = new Local(['datadir' => '/']);
481
-			return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
482
-		} else {
483
-			return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
484
-		}
485
-	}
486
-
487
-	public function writeStream(string $path, $stream, int $size = null): int {
488
-		return (int)file_put_contents($this->getSourcePath($path), $stream);
489
-	}
52
+    protected $datadir;
53
+
54
+    protected $dataDirLength;
55
+
56
+    protected $allowSymlinks = false;
57
+
58
+    protected $realDataDir;
59
+
60
+    public function __construct($arguments) {
61
+        if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
62
+            throw new \InvalidArgumentException('No data directory set for local storage');
63
+        }
64
+        $this->datadir = str_replace('//', '/', $arguments['datadir']);
65
+        // some crazy code uses a local storage on root...
66
+        if ($this->datadir === '/') {
67
+            $this->realDataDir = $this->datadir;
68
+        } else {
69
+            $realPath = realpath($this->datadir) ?: $this->datadir;
70
+            $this->realDataDir = rtrim($realPath, '/') . '/';
71
+        }
72
+        if (substr($this->datadir, -1) !== '/') {
73
+            $this->datadir .= '/';
74
+        }
75
+        $this->dataDirLength = strlen($this->realDataDir);
76
+    }
77
+
78
+    public function __destruct() {
79
+    }
80
+
81
+    public function getId() {
82
+        return 'local::' . $this->datadir;
83
+    }
84
+
85
+    public function mkdir($path) {
86
+        return @mkdir($this->getSourcePath($path), 0777, true);
87
+    }
88
+
89
+    public function rmdir($path) {
90
+        if (!$this->isDeletable($path)) {
91
+            return false;
92
+        }
93
+        try {
94
+            $it = new \RecursiveIteratorIterator(
95
+                new \RecursiveDirectoryIterator($this->getSourcePath($path)),
96
+                \RecursiveIteratorIterator::CHILD_FIRST
97
+            );
98
+            /**
99
+             * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
100
+             * This bug is fixed in PHP 5.5.9 or before
101
+             * See #8376
102
+             */
103
+            $it->rewind();
104
+            while ($it->valid()) {
105
+                /**
106
+                 * @var \SplFileInfo $file
107
+                 */
108
+                $file = $it->current();
109
+                if (in_array($file->getBasename(), array('.', '..'))) {
110
+                    $it->next();
111
+                    continue;
112
+                } elseif ($file->isDir()) {
113
+                    rmdir($file->getPathname());
114
+                } elseif ($file->isFile() || $file->isLink()) {
115
+                    unlink($file->getPathname());
116
+                }
117
+                $it->next();
118
+            }
119
+            return rmdir($this->getSourcePath($path));
120
+        } catch (\UnexpectedValueException $e) {
121
+            return false;
122
+        }
123
+    }
124
+
125
+    public function opendir($path) {
126
+        return opendir($this->getSourcePath($path));
127
+    }
128
+
129
+    public function is_dir($path) {
130
+        if (substr($path, -1) == '/') {
131
+            $path = substr($path, 0, -1);
132
+        }
133
+        return is_dir($this->getSourcePath($path));
134
+    }
135
+
136
+    public function is_file($path) {
137
+        return is_file($this->getSourcePath($path));
138
+    }
139
+
140
+    public function stat($path) {
141
+        clearstatcache();
142
+        $fullPath = $this->getSourcePath($path);
143
+        $statResult = stat($fullPath);
144
+        if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) {
145
+            $filesize = $this->filesize($path);
146
+            $statResult['size'] = $filesize;
147
+            $statResult[7] = $filesize;
148
+        }
149
+        return $statResult;
150
+    }
151
+
152
+    public function filetype($path) {
153
+        $filetype = filetype($this->getSourcePath($path));
154
+        if ($filetype == 'link') {
155
+            $filetype = filetype(realpath($this->getSourcePath($path)));
156
+        }
157
+        return $filetype;
158
+    }
159
+
160
+    public function filesize($path) {
161
+        if ($this->is_dir($path)) {
162
+            return 0;
163
+        }
164
+        $fullPath = $this->getSourcePath($path);
165
+        if (PHP_INT_SIZE === 4) {
166
+            $helper = new \OC\LargeFileHelper;
167
+            return $helper->getFileSize($fullPath);
168
+        }
169
+        return filesize($fullPath);
170
+    }
171
+
172
+    public function isReadable($path) {
173
+        return is_readable($this->getSourcePath($path));
174
+    }
175
+
176
+    public function isUpdatable($path) {
177
+        return is_writable($this->getSourcePath($path));
178
+    }
179
+
180
+    public function file_exists($path) {
181
+        return file_exists($this->getSourcePath($path));
182
+    }
183
+
184
+    public function filemtime($path) {
185
+        $fullPath = $this->getSourcePath($path);
186
+        clearstatcache(true, $fullPath);
187
+        if (!$this->file_exists($path)) {
188
+            return false;
189
+        }
190
+        if (PHP_INT_SIZE === 4) {
191
+            $helper = new \OC\LargeFileHelper();
192
+            return $helper->getFileMtime($fullPath);
193
+        }
194
+        return filemtime($fullPath);
195
+    }
196
+
197
+    public function touch($path, $mtime = null) {
198
+        // sets the modification time of the file to the given value.
199
+        // If mtime is nil the current time is set.
200
+        // note that the access time of the file always changes to the current time.
201
+        if ($this->file_exists($path) and !$this->isUpdatable($path)) {
202
+            return false;
203
+        }
204
+        if (!is_null($mtime)) {
205
+            $result = @touch($this->getSourcePath($path), $mtime);
206
+        } else {
207
+            $result = @touch($this->getSourcePath($path));
208
+        }
209
+        if ($result) {
210
+            clearstatcache(true, $this->getSourcePath($path));
211
+        }
212
+
213
+        return $result;
214
+    }
215
+
216
+    public function file_get_contents($path) {
217
+        return file_get_contents($this->getSourcePath($path));
218
+    }
219
+
220
+    public function file_put_contents($path, $data) {
221
+        return file_put_contents($this->getSourcePath($path), $data);
222
+    }
223
+
224
+    public function unlink($path) {
225
+        if ($this->is_dir($path)) {
226
+            return $this->rmdir($path);
227
+        } else if ($this->is_file($path)) {
228
+            return unlink($this->getSourcePath($path));
229
+        } else {
230
+            return false;
231
+        }
232
+
233
+    }
234
+
235
+    private function treeContainsBlacklistedFile(string $path): bool {
236
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
237
+        foreach ($iterator as $file) {
238
+            /** @var \SplFileInfo $file */
239
+            if (Filesystem::isFileBlacklisted($file->getBasename())) {
240
+                return true;
241
+            }
242
+        }
243
+
244
+        return false;
245
+    }
246
+
247
+    public function rename($path1, $path2) {
248
+        $srcParent = dirname($path1);
249
+        $dstParent = dirname($path2);
250
+
251
+        if (!$this->isUpdatable($srcParent)) {
252
+            \OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, ILogger::ERROR);
253
+            return false;
254
+        }
255
+
256
+        if (!$this->isUpdatable($dstParent)) {
257
+            \OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, ILogger::ERROR);
258
+            return false;
259
+        }
260
+
261
+        if (!$this->file_exists($path1)) {
262
+            \OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, ILogger::ERROR);
263
+            return false;
264
+        }
265
+
266
+        if ($this->is_dir($path2)) {
267
+            $this->rmdir($path2);
268
+        } else if ($this->is_file($path2)) {
269
+            $this->unlink($path2);
270
+        }
271
+
272
+        if ($this->is_dir($path1)) {
273
+            // we can't move folders across devices, use copy instead
274
+            $stat1 = stat(dirname($this->getSourcePath($path1)));
275
+            $stat2 = stat(dirname($this->getSourcePath($path2)));
276
+            if ($stat1['dev'] !== $stat2['dev']) {
277
+                $result = $this->copy($path1, $path2);
278
+                if ($result) {
279
+                    $result &= $this->rmdir($path1);
280
+                }
281
+                return $result;
282
+            }
283
+
284
+            if ($this->treeContainsBlacklistedFile($this->getSourcePath($path1))) {
285
+                throw new ForbiddenException('Invalid path', false);
286
+            }
287
+        }
288
+
289
+        return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
290
+    }
291
+
292
+    public function copy($path1, $path2) {
293
+        if ($this->is_dir($path1)) {
294
+            return parent::copy($path1, $path2);
295
+        } else {
296
+            return copy($this->getSourcePath($path1), $this->getSourcePath($path2));
297
+        }
298
+    }
299
+
300
+    public function fopen($path, $mode) {
301
+        return fopen($this->getSourcePath($path), $mode);
302
+    }
303
+
304
+    public function hash($type, $path, $raw = false) {
305
+        return hash_file($type, $this->getSourcePath($path), $raw);
306
+    }
307
+
308
+    public function free_space($path) {
309
+        $sourcePath = $this->getSourcePath($path);
310
+        // using !is_dir because $sourcePath might be a part file or
311
+        // non-existing file, so we'd still want to use the parent dir
312
+        // in such cases
313
+        if (!is_dir($sourcePath)) {
314
+            // disk_free_space doesn't work on files
315
+            $sourcePath = dirname($sourcePath);
316
+        }
317
+        $space = @disk_free_space($sourcePath);
318
+        if ($space === false || is_null($space)) {
319
+            return \OCP\Files\FileInfo::SPACE_UNKNOWN;
320
+        }
321
+        return $space;
322
+    }
323
+
324
+    public function search($query) {
325
+        return $this->searchInDir($query);
326
+    }
327
+
328
+    public function getLocalFile($path) {
329
+        return $this->getSourcePath($path);
330
+    }
331
+
332
+    public function getLocalFolder($path) {
333
+        return $this->getSourcePath($path);
334
+    }
335
+
336
+    /**
337
+     * @param string $query
338
+     * @param string $dir
339
+     * @return array
340
+     */
341
+    protected function searchInDir($query, $dir = '') {
342
+        $files = array();
343
+        $physicalDir = $this->getSourcePath($dir);
344
+        foreach (scandir($physicalDir) as $item) {
345
+            if (\OC\Files\Filesystem::isIgnoredDir($item))
346
+                continue;
347
+            $physicalItem = $physicalDir . '/' . $item;
348
+
349
+            if (strstr(strtolower($item), strtolower($query)) !== false) {
350
+                $files[] = $dir . '/' . $item;
351
+            }
352
+            if (is_dir($physicalItem)) {
353
+                $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
354
+            }
355
+        }
356
+        return $files;
357
+    }
358
+
359
+    /**
360
+     * check if a file or folder has been updated since $time
361
+     *
362
+     * @param string $path
363
+     * @param int $time
364
+     * @return bool
365
+     */
366
+    public function hasUpdated($path, $time) {
367
+        if ($this->file_exists($path)) {
368
+            return $this->filemtime($path) > $time;
369
+        } else {
370
+            return true;
371
+        }
372
+    }
373
+
374
+    /**
375
+     * Get the source path (on disk) of a given path
376
+     *
377
+     * @param string $path
378
+     * @return string
379
+     * @throws ForbiddenException
380
+     */
381
+    public function getSourcePath($path) {
382
+        if (Filesystem::isFileBlacklisted($path)) {
383
+            throw new ForbiddenException('Invalid path', false);
384
+        }
385
+
386
+        $fullPath = $this->datadir . $path;
387
+        $currentPath = $path;
388
+        if ($this->allowSymlinks || $currentPath === '') {
389
+            return $fullPath;
390
+        }
391
+        $pathToResolve = $fullPath;
392
+        $realPath = realpath($pathToResolve);
393
+        while ($realPath === false) { // for non existing files check the parent directory
394
+            $currentPath = dirname($currentPath);
395
+            if ($currentPath === '' || $currentPath === '.') {
396
+                return $fullPath;
397
+            }
398
+            $realPath = realpath($this->datadir . $currentPath);
399
+        }
400
+        if ($realPath) {
401
+            $realPath = $realPath . '/';
402
+        }
403
+        if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
404
+            return $fullPath;
405
+        }
406
+
407
+        \OCP\Util::writeLog('core', "Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ILogger::ERROR);
408
+        throw new ForbiddenException('Following symlinks is not allowed', false);
409
+    }
410
+
411
+    /**
412
+     * {@inheritdoc}
413
+     */
414
+    public function isLocal() {
415
+        return true;
416
+    }
417
+
418
+    /**
419
+     * get the ETag for a file or folder
420
+     *
421
+     * @param string $path
422
+     * @return string
423
+     */
424
+    public function getETag($path) {
425
+        if ($this->is_file($path)) {
426
+            $stat = $this->stat($path);
427
+            return md5(
428
+                $stat['mtime'] .
429
+                $stat['ino'] .
430
+                $stat['dev'] .
431
+                $stat['size']
432
+            );
433
+        } else {
434
+            return parent::getETag($path);
435
+        }
436
+    }
437
+
438
+    /**
439
+     * @param IStorage $sourceStorage
440
+     * @param string $sourceInternalPath
441
+     * @param string $targetInternalPath
442
+     * @param bool $preserveMtime
443
+     * @return bool
444
+     */
445
+    public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
446
+        if ($sourceStorage->instanceOfStorage(Local::class)) {
447
+            if ($sourceStorage->instanceOfStorage(Jail::class)) {
448
+                /**
449
+                 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
450
+                 */
451
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
452
+            }
453
+            /**
454
+             * @var \OC\Files\Storage\Local $sourceStorage
455
+             */
456
+            $rootStorage = new Local(['datadir' => '/']);
457
+            return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
458
+        } else {
459
+            return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
460
+        }
461
+    }
462
+
463
+    /**
464
+     * @param IStorage $sourceStorage
465
+     * @param string $sourceInternalPath
466
+     * @param string $targetInternalPath
467
+     * @return bool
468
+     */
469
+    public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
470
+        if ($sourceStorage->instanceOfStorage(Local::class)) {
471
+            if ($sourceStorage->instanceOfStorage(Jail::class)) {
472
+                /**
473
+                 * @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
474
+                 */
475
+                $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
476
+            }
477
+            /**
478
+             * @var \OC\Files\Storage\Local $sourceStorage
479
+             */
480
+            $rootStorage = new Local(['datadir' => '/']);
481
+            return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
482
+        } else {
483
+            return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
484
+        }
485
+    }
486
+
487
+    public function writeStream(string $path, $stream, int $size = null): int {
488
+        return (int)file_put_contents($this->getSourcePath($path), $stream);
489
+    }
490 490
 }
Please login to merge, or discard this patch.