Passed
Push — master ( 62403d...0c3e2f )
by Joas
14:50 queued 14s
created
apps/dav/lib/Connector/Sabre/Node.php 1 patch
Indentation   +363 added lines, -363 removed lines patch added patch discarded remove patch
@@ -49,373 +49,373 @@
 block discarded – undo
49 49
 
50 50
 abstract class Node implements \Sabre\DAV\INode {
51 51
 
52
-	/**
53
-	 * @var \OC\Files\View
54
-	 */
55
-	protected $fileView;
56
-
57
-	/**
58
-	 * The path to the current node
59
-	 *
60
-	 * @var string
61
-	 */
62
-	protected $path;
63
-
64
-	/**
65
-	 * node properties cache
66
-	 *
67
-	 * @var array
68
-	 */
69
-	protected $property_cache = null;
70
-
71
-	/**
72
-	 * @var \OCP\Files\FileInfo
73
-	 */
74
-	protected $info;
75
-
76
-	/**
77
-	 * @var IManager
78
-	 */
79
-	protected $shareManager;
80
-
81
-	/**
82
-	 * Sets up the node, expects a full path name
83
-	 *
84
-	 * @param \OC\Files\View $view
85
-	 * @param \OCP\Files\FileInfo $info
86
-	 * @param IManager $shareManager
87
-	 */
88
-	public function __construct(View $view, FileInfo $info, IManager $shareManager = null) {
89
-		$this->fileView = $view;
90
-		$this->path = $this->fileView->getRelativePath($info->getPath());
91
-		$this->info = $info;
92
-		if ($shareManager) {
93
-			$this->shareManager = $shareManager;
94
-		} else {
95
-			$this->shareManager = \OC::$server->getShareManager();
96
-		}
97
-	}
98
-
99
-	protected function refreshInfo() {
100
-		$this->info = $this->fileView->getFileInfo($this->path);
101
-	}
102
-
103
-	/**
104
-	 *  Returns the name of the node
105
-	 *
106
-	 * @return string
107
-	 */
108
-	public function getName() {
109
-		return $this->info->getName();
110
-	}
111
-
112
-	/**
113
-	 * Returns the full path
114
-	 *
115
-	 * @return string
116
-	 */
117
-	public function getPath() {
118
-		return $this->path;
119
-	}
120
-
121
-	/**
122
-	 * Renames the node
123
-	 *
124
-	 * @param string $name The new name
125
-	 * @throws \Sabre\DAV\Exception\BadRequest
126
-	 * @throws \Sabre\DAV\Exception\Forbidden
127
-	 */
128
-	public function setName($name) {
129
-
130
-		// rename is only allowed if the update privilege is granted
131
-		if (!$this->info->isUpdateable()) {
132
-			throw new \Sabre\DAV\Exception\Forbidden();
133
-		}
134
-
135
-		list($parentPath,) = \Sabre\Uri\split($this->path);
136
-		list(, $newName) = \Sabre\Uri\split($name);
137
-
138
-		// verify path of the target
139
-		$this->verifyPath();
140
-
141
-		$newPath = $parentPath . '/' . $newName;
142
-
143
-		if (!$this->fileView->rename($this->path, $newPath)) {
144
-			throw new \Sabre\DAV\Exception('Failed to rename '. $this->path . ' to ' . $newPath);
145
-		}
146
-
147
-		$this->path = $newPath;
148
-
149
-		$this->refreshInfo();
150
-	}
151
-
152
-	public function setPropertyCache($property_cache) {
153
-		$this->property_cache = $property_cache;
154
-	}
155
-
156
-	/**
157
-	 * Returns the last modification time, as a unix timestamp
158
-	 *
159
-	 * @return int timestamp as integer
160
-	 */
161
-	public function getLastModified() {
162
-		$timestamp = $this->info->getMtime();
163
-		if (!empty($timestamp)) {
164
-			return (int)$timestamp;
165
-		}
166
-		return $timestamp;
167
-	}
168
-
169
-	/**
170
-	 *  sets the last modification time of the file (mtime) to the value given
171
-	 *  in the second parameter or to now if the second param is empty.
172
-	 *  Even if the modification time is set to a custom value the access time is set to now.
173
-	 */
174
-	public function touch($mtime) {
175
-		$mtime = $this->sanitizeMtime($mtime);
176
-		$this->fileView->touch($this->path, $mtime);
177
-		$this->refreshInfo();
178
-	}
179
-
180
-	/**
181
-	 * Returns the ETag for a file
182
-	 *
183
-	 * An ETag is a unique identifier representing the current version of the
184
-	 * file. If the file changes, the ETag MUST change.  The ETag is an
185
-	 * arbitrary string, but MUST be surrounded by double-quotes.
186
-	 *
187
-	 * Return null if the ETag can not effectively be determined
188
-	 *
189
-	 * @return string
190
-	 */
191
-	public function getETag() {
192
-		return '"' . $this->info->getEtag() . '"';
193
-	}
194
-
195
-	/**
196
-	 * Sets the ETag
197
-	 *
198
-	 * @param string $etag
199
-	 *
200
-	 * @return int file id of updated file or -1 on failure
201
-	 */
202
-	public function setETag($etag) {
203
-		return $this->fileView->putFileInfo($this->path, ['etag' => $etag]);
204
-	}
205
-
206
-	public function setCreationTime(int $time) {
207
-		return $this->fileView->putFileInfo($this->path, ['creation_time' => $time]);
208
-	}
209
-
210
-	public function setUploadTime(int $time) {
211
-		return $this->fileView->putFileInfo($this->path, ['upload_time' => $time]);
212
-	}
213
-
214
-	/**
215
-	 * Returns the size of the node, in bytes
216
-	 *
217
-	 * @return integer
218
-	 */
219
-	public function getSize() {
220
-		return $this->info->getSize();
221
-	}
222
-
223
-	/**
224
-	 * Returns the cache's file id
225
-	 *
226
-	 * @return int
227
-	 */
228
-	public function getId() {
229
-		return $this->info->getId();
230
-	}
231
-
232
-	/**
233
-	 * @return string|null
234
-	 */
235
-	public function getFileId() {
236
-		if ($this->info->getId()) {
237
-			$instanceId = \OC_Util::getInstanceId();
238
-			$id = sprintf('%08d', $this->info->getId());
239
-			return $id . $instanceId;
240
-		}
241
-
242
-		return null;
243
-	}
244
-
245
-	/**
246
-	 * @return integer
247
-	 */
248
-	public function getInternalFileId() {
249
-		return $this->info->getId();
250
-	}
251
-
252
-	/**
253
-	 * @param string $user
254
-	 * @return int
255
-	 */
256
-	public function getSharePermissions($user) {
257
-
258
-		// check of we access a federated share
259
-		if ($user !== null) {
260
-			try {
261
-				$share = $this->shareManager->getShareByToken($user);
262
-				return $share->getPermissions();
263
-			} catch (ShareNotFound $e) {
264
-				// ignore
265
-			}
266
-		}
267
-
268
-		try {
269
-			$storage = $this->info->getStorage();
270
-		} catch (StorageNotAvailableException $e) {
271
-			$storage = null;
272
-		}
273
-
274
-		if ($storage && $storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
275
-			/** @var \OCA\Files_Sharing\SharedStorage $storage */
276
-			$permissions = (int)$storage->getShare()->getPermissions();
277
-		} else {
278
-			$permissions = $this->info->getPermissions();
279
-		}
280
-
281
-		/*
52
+    /**
53
+     * @var \OC\Files\View
54
+     */
55
+    protected $fileView;
56
+
57
+    /**
58
+     * The path to the current node
59
+     *
60
+     * @var string
61
+     */
62
+    protected $path;
63
+
64
+    /**
65
+     * node properties cache
66
+     *
67
+     * @var array
68
+     */
69
+    protected $property_cache = null;
70
+
71
+    /**
72
+     * @var \OCP\Files\FileInfo
73
+     */
74
+    protected $info;
75
+
76
+    /**
77
+     * @var IManager
78
+     */
79
+    protected $shareManager;
80
+
81
+    /**
82
+     * Sets up the node, expects a full path name
83
+     *
84
+     * @param \OC\Files\View $view
85
+     * @param \OCP\Files\FileInfo $info
86
+     * @param IManager $shareManager
87
+     */
88
+    public function __construct(View $view, FileInfo $info, IManager $shareManager = null) {
89
+        $this->fileView = $view;
90
+        $this->path = $this->fileView->getRelativePath($info->getPath());
91
+        $this->info = $info;
92
+        if ($shareManager) {
93
+            $this->shareManager = $shareManager;
94
+        } else {
95
+            $this->shareManager = \OC::$server->getShareManager();
96
+        }
97
+    }
98
+
99
+    protected function refreshInfo() {
100
+        $this->info = $this->fileView->getFileInfo($this->path);
101
+    }
102
+
103
+    /**
104
+     *  Returns the name of the node
105
+     *
106
+     * @return string
107
+     */
108
+    public function getName() {
109
+        return $this->info->getName();
110
+    }
111
+
112
+    /**
113
+     * Returns the full path
114
+     *
115
+     * @return string
116
+     */
117
+    public function getPath() {
118
+        return $this->path;
119
+    }
120
+
121
+    /**
122
+     * Renames the node
123
+     *
124
+     * @param string $name The new name
125
+     * @throws \Sabre\DAV\Exception\BadRequest
126
+     * @throws \Sabre\DAV\Exception\Forbidden
127
+     */
128
+    public function setName($name) {
129
+
130
+        // rename is only allowed if the update privilege is granted
131
+        if (!$this->info->isUpdateable()) {
132
+            throw new \Sabre\DAV\Exception\Forbidden();
133
+        }
134
+
135
+        list($parentPath,) = \Sabre\Uri\split($this->path);
136
+        list(, $newName) = \Sabre\Uri\split($name);
137
+
138
+        // verify path of the target
139
+        $this->verifyPath();
140
+
141
+        $newPath = $parentPath . '/' . $newName;
142
+
143
+        if (!$this->fileView->rename($this->path, $newPath)) {
144
+            throw new \Sabre\DAV\Exception('Failed to rename '. $this->path . ' to ' . $newPath);
145
+        }
146
+
147
+        $this->path = $newPath;
148
+
149
+        $this->refreshInfo();
150
+    }
151
+
152
+    public function setPropertyCache($property_cache) {
153
+        $this->property_cache = $property_cache;
154
+    }
155
+
156
+    /**
157
+     * Returns the last modification time, as a unix timestamp
158
+     *
159
+     * @return int timestamp as integer
160
+     */
161
+    public function getLastModified() {
162
+        $timestamp = $this->info->getMtime();
163
+        if (!empty($timestamp)) {
164
+            return (int)$timestamp;
165
+        }
166
+        return $timestamp;
167
+    }
168
+
169
+    /**
170
+     *  sets the last modification time of the file (mtime) to the value given
171
+     *  in the second parameter or to now if the second param is empty.
172
+     *  Even if the modification time is set to a custom value the access time is set to now.
173
+     */
174
+    public function touch($mtime) {
175
+        $mtime = $this->sanitizeMtime($mtime);
176
+        $this->fileView->touch($this->path, $mtime);
177
+        $this->refreshInfo();
178
+    }
179
+
180
+    /**
181
+     * Returns the ETag for a file
182
+     *
183
+     * An ETag is a unique identifier representing the current version of the
184
+     * file. If the file changes, the ETag MUST change.  The ETag is an
185
+     * arbitrary string, but MUST be surrounded by double-quotes.
186
+     *
187
+     * Return null if the ETag can not effectively be determined
188
+     *
189
+     * @return string
190
+     */
191
+    public function getETag() {
192
+        return '"' . $this->info->getEtag() . '"';
193
+    }
194
+
195
+    /**
196
+     * Sets the ETag
197
+     *
198
+     * @param string $etag
199
+     *
200
+     * @return int file id of updated file or -1 on failure
201
+     */
202
+    public function setETag($etag) {
203
+        return $this->fileView->putFileInfo($this->path, ['etag' => $etag]);
204
+    }
205
+
206
+    public function setCreationTime(int $time) {
207
+        return $this->fileView->putFileInfo($this->path, ['creation_time' => $time]);
208
+    }
209
+
210
+    public function setUploadTime(int $time) {
211
+        return $this->fileView->putFileInfo($this->path, ['upload_time' => $time]);
212
+    }
213
+
214
+    /**
215
+     * Returns the size of the node, in bytes
216
+     *
217
+     * @return integer
218
+     */
219
+    public function getSize() {
220
+        return $this->info->getSize();
221
+    }
222
+
223
+    /**
224
+     * Returns the cache's file id
225
+     *
226
+     * @return int
227
+     */
228
+    public function getId() {
229
+        return $this->info->getId();
230
+    }
231
+
232
+    /**
233
+     * @return string|null
234
+     */
235
+    public function getFileId() {
236
+        if ($this->info->getId()) {
237
+            $instanceId = \OC_Util::getInstanceId();
238
+            $id = sprintf('%08d', $this->info->getId());
239
+            return $id . $instanceId;
240
+        }
241
+
242
+        return null;
243
+    }
244
+
245
+    /**
246
+     * @return integer
247
+     */
248
+    public function getInternalFileId() {
249
+        return $this->info->getId();
250
+    }
251
+
252
+    /**
253
+     * @param string $user
254
+     * @return int
255
+     */
256
+    public function getSharePermissions($user) {
257
+
258
+        // check of we access a federated share
259
+        if ($user !== null) {
260
+            try {
261
+                $share = $this->shareManager->getShareByToken($user);
262
+                return $share->getPermissions();
263
+            } catch (ShareNotFound $e) {
264
+                // ignore
265
+            }
266
+        }
267
+
268
+        try {
269
+            $storage = $this->info->getStorage();
270
+        } catch (StorageNotAvailableException $e) {
271
+            $storage = null;
272
+        }
273
+
274
+        if ($storage && $storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
275
+            /** @var \OCA\Files_Sharing\SharedStorage $storage */
276
+            $permissions = (int)$storage->getShare()->getPermissions();
277
+        } else {
278
+            $permissions = $this->info->getPermissions();
279
+        }
280
+
281
+        /*
282 282
 		 * We can always share non moveable mount points with DELETE and UPDATE
283 283
 		 * Eventually we need to do this properly
284 284
 		 */
285
-		$mountpoint = $this->info->getMountPoint();
286
-		if (!($mountpoint instanceof MoveableMount)) {
287
-			$mountpointpath = $mountpoint->getMountPoint();
288
-			if (substr($mountpointpath, -1) === '/') {
289
-				$mountpointpath = substr($mountpointpath, 0, -1);
290
-			}
291
-
292
-			if (!$mountpoint->getOption('readonly', false) && $mountpointpath === $this->info->getPath()) {
293
-				$permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
294
-			}
295
-		}
296
-
297
-		/*
285
+        $mountpoint = $this->info->getMountPoint();
286
+        if (!($mountpoint instanceof MoveableMount)) {
287
+            $mountpointpath = $mountpoint->getMountPoint();
288
+            if (substr($mountpointpath, -1) === '/') {
289
+                $mountpointpath = substr($mountpointpath, 0, -1);
290
+            }
291
+
292
+            if (!$mountpoint->getOption('readonly', false) && $mountpointpath === $this->info->getPath()) {
293
+                $permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
294
+            }
295
+        }
296
+
297
+        /*
298 298
 		 * Files can't have create or delete permissions
299 299
 		 */
300
-		if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) {
301
-			$permissions &= ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE);
302
-		}
303
-
304
-		return $permissions;
305
-	}
306
-
307
-	/**
308
-	 * @param string $user
309
-	 * @return string
310
-	 */
311
-	public function getNoteFromShare($user) {
312
-		if ($user === null) {
313
-			return '';
314
-		}
315
-
316
-		$types = [
317
-			Share::SHARE_TYPE_USER,
318
-			Share::SHARE_TYPE_GROUP,
319
-			Share::SHARE_TYPE_CIRCLE,
320
-			Share::SHARE_TYPE_ROOM
321
-		];
322
-
323
-		foreach ($types as $shareType) {
324
-			$shares = $this->shareManager->getSharedWith($user, $shareType, $this, -1);
325
-			foreach ($shares as $share) {
326
-				$note = $share->getNote();
327
-				if($share->getShareOwner() !== $user && !empty($note)) {
328
-					return $note;
329
-				}
330
-			}
331
-		}
332
-
333
-		return '';
334
-	}
335
-
336
-	/**
337
-	 * @return string
338
-	 */
339
-	public function getDavPermissions() {
340
-		$p = '';
341
-		if ($this->info->isShared()) {
342
-			$p .= 'S';
343
-		}
344
-		if ($this->info->isShareable()) {
345
-			$p .= 'R';
346
-		}
347
-		if ($this->info->isMounted()) {
348
-			$p .= 'M';
349
-		}
350
-		if ($this->info->isReadable()) {
351
-			$p .= 'G';
352
-		}
353
-		if ($this->info->isDeletable()) {
354
-			$p .= 'D';
355
-		}
356
-		if ($this->info->isUpdateable()) {
357
-			$p .= 'NV'; // Renameable, Moveable
358
-		}
359
-		if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) {
360
-			if ($this->info->isUpdateable()) {
361
-				$p .= 'W';
362
-			}
363
-		} else {
364
-			if ($this->info->isCreatable()) {
365
-				$p .= 'CK';
366
-			}
367
-		}
368
-		return $p;
369
-	}
370
-
371
-	public function getOwner() {
372
-		return $this->info->getOwner();
373
-	}
374
-
375
-	protected function verifyPath() {
376
-		try {
377
-			$fileName = basename($this->info->getPath());
378
-			$this->fileView->verifyPath($this->path, $fileName);
379
-		} catch (\OCP\Files\InvalidPathException $ex) {
380
-			throw new InvalidPath($ex->getMessage());
381
-		}
382
-	}
383
-
384
-	/**
385
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
386
-	 */
387
-	public function acquireLock($type) {
388
-		$this->fileView->lockFile($this->path, $type);
389
-	}
390
-
391
-	/**
392
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
393
-	 */
394
-	public function releaseLock($type) {
395
-		$this->fileView->unlockFile($this->path, $type);
396
-	}
397
-
398
-	/**
399
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
400
-	 */
401
-	public function changeLock($type) {
402
-		$this->fileView->changeLock($this->path, $type);
403
-	}
404
-
405
-	public function getFileInfo() {
406
-		return $this->info;
407
-	}
408
-
409
-	protected function sanitizeMtime($mtimeFromRequest) {
410
-		// In PHP 5.X "is_numeric" returns true for strings in hexadecimal
411
-		// notation. This is no longer the case in PHP 7.X, so this check
412
-		// ensures that strings with hexadecimal notations fail too in PHP 5.X.
413
-		$isHexadecimal = is_string($mtimeFromRequest) && preg_match('/^\s*0[xX]/', $mtimeFromRequest);
414
-		if ($isHexadecimal || !is_numeric($mtimeFromRequest)) {
415
-			throw new \InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).');
416
-		}
417
-
418
-		return (int)$mtimeFromRequest;
419
-	}
300
+        if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) {
301
+            $permissions &= ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE);
302
+        }
303
+
304
+        return $permissions;
305
+    }
306
+
307
+    /**
308
+     * @param string $user
309
+     * @return string
310
+     */
311
+    public function getNoteFromShare($user) {
312
+        if ($user === null) {
313
+            return '';
314
+        }
315
+
316
+        $types = [
317
+            Share::SHARE_TYPE_USER,
318
+            Share::SHARE_TYPE_GROUP,
319
+            Share::SHARE_TYPE_CIRCLE,
320
+            Share::SHARE_TYPE_ROOM
321
+        ];
322
+
323
+        foreach ($types as $shareType) {
324
+            $shares = $this->shareManager->getSharedWith($user, $shareType, $this, -1);
325
+            foreach ($shares as $share) {
326
+                $note = $share->getNote();
327
+                if($share->getShareOwner() !== $user && !empty($note)) {
328
+                    return $note;
329
+                }
330
+            }
331
+        }
332
+
333
+        return '';
334
+    }
335
+
336
+    /**
337
+     * @return string
338
+     */
339
+    public function getDavPermissions() {
340
+        $p = '';
341
+        if ($this->info->isShared()) {
342
+            $p .= 'S';
343
+        }
344
+        if ($this->info->isShareable()) {
345
+            $p .= 'R';
346
+        }
347
+        if ($this->info->isMounted()) {
348
+            $p .= 'M';
349
+        }
350
+        if ($this->info->isReadable()) {
351
+            $p .= 'G';
352
+        }
353
+        if ($this->info->isDeletable()) {
354
+            $p .= 'D';
355
+        }
356
+        if ($this->info->isUpdateable()) {
357
+            $p .= 'NV'; // Renameable, Moveable
358
+        }
359
+        if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) {
360
+            if ($this->info->isUpdateable()) {
361
+                $p .= 'W';
362
+            }
363
+        } else {
364
+            if ($this->info->isCreatable()) {
365
+                $p .= 'CK';
366
+            }
367
+        }
368
+        return $p;
369
+    }
370
+
371
+    public function getOwner() {
372
+        return $this->info->getOwner();
373
+    }
374
+
375
+    protected function verifyPath() {
376
+        try {
377
+            $fileName = basename($this->info->getPath());
378
+            $this->fileView->verifyPath($this->path, $fileName);
379
+        } catch (\OCP\Files\InvalidPathException $ex) {
380
+            throw new InvalidPath($ex->getMessage());
381
+        }
382
+    }
383
+
384
+    /**
385
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
386
+     */
387
+    public function acquireLock($type) {
388
+        $this->fileView->lockFile($this->path, $type);
389
+    }
390
+
391
+    /**
392
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
393
+     */
394
+    public function releaseLock($type) {
395
+        $this->fileView->unlockFile($this->path, $type);
396
+    }
397
+
398
+    /**
399
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
400
+     */
401
+    public function changeLock($type) {
402
+        $this->fileView->changeLock($this->path, $type);
403
+    }
404
+
405
+    public function getFileInfo() {
406
+        return $this->info;
407
+    }
408
+
409
+    protected function sanitizeMtime($mtimeFromRequest) {
410
+        // In PHP 5.X "is_numeric" returns true for strings in hexadecimal
411
+        // notation. This is no longer the case in PHP 7.X, so this check
412
+        // ensures that strings with hexadecimal notations fail too in PHP 5.X.
413
+        $isHexadecimal = is_string($mtimeFromRequest) && preg_match('/^\s*0[xX]/', $mtimeFromRequest);
414
+        if ($isHexadecimal || !is_numeric($mtimeFromRequest)) {
415
+            throw new \InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).');
416
+        }
417
+
418
+        return (int)$mtimeFromRequest;
419
+    }
420 420
 
421 421
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/AppEnabledPlugin.php 1 patch
Indentation   +48 added lines, -48 removed lines patch added patch discarded remove patch
@@ -34,58 +34,58 @@
 block discarded – undo
34 34
  */
35 35
 class AppEnabledPlugin extends ServerPlugin {
36 36
 
37
-	/**
38
-	 * Reference to main server object
39
-	 *
40
-	 * @var \Sabre\DAV\Server
41
-	 */
42
-	private $server;
37
+    /**
38
+     * Reference to main server object
39
+     *
40
+     * @var \Sabre\DAV\Server
41
+     */
42
+    private $server;
43 43
 
44
-	/**
45
-	 * @var string
46
-	 */
47
-	private $app;
44
+    /**
45
+     * @var string
46
+     */
47
+    private $app;
48 48
 
49
-	/**
50
-	 * @var \OCP\App\IAppManager
51
-	 */
52
-	private $appManager;
49
+    /**
50
+     * @var \OCP\App\IAppManager
51
+     */
52
+    private $appManager;
53 53
 
54
-	/**
55
-	 * @param string $app
56
-	 * @param \OCP\App\IAppManager $appManager
57
-	 */
58
-	public function __construct($app, IAppManager $appManager) {
59
-		$this->app = $app;
60
-		$this->appManager = $appManager;
61
-	}
54
+    /**
55
+     * @param string $app
56
+     * @param \OCP\App\IAppManager $appManager
57
+     */
58
+    public function __construct($app, IAppManager $appManager) {
59
+        $this->app = $app;
60
+        $this->appManager = $appManager;
61
+    }
62 62
 
63
-	/**
64
-	 * This initializes the plugin.
65
-	 *
66
-	 * This function is called by \Sabre\DAV\Server, after
67
-	 * addPlugin is called.
68
-	 *
69
-	 * This method should set up the required event subscriptions.
70
-	 *
71
-	 * @param \Sabre\DAV\Server $server
72
-	 * @return void
73
-	 */
74
-	public function initialize(\Sabre\DAV\Server $server) {
63
+    /**
64
+     * This initializes the plugin.
65
+     *
66
+     * This function is called by \Sabre\DAV\Server, after
67
+     * addPlugin is called.
68
+     *
69
+     * This method should set up the required event subscriptions.
70
+     *
71
+     * @param \Sabre\DAV\Server $server
72
+     * @return void
73
+     */
74
+    public function initialize(\Sabre\DAV\Server $server) {
75 75
 
76
-		$this->server = $server;
77
-		$this->server->on('beforeMethod', [$this, 'checkAppEnabled'], 30);
78
-	}
76
+        $this->server = $server;
77
+        $this->server->on('beforeMethod', [$this, 'checkAppEnabled'], 30);
78
+    }
79 79
 
80
-	/**
81
-	 * This method is called before any HTTP after auth and checks if the user has access to the app
82
-	 *
83
-	 * @throws \Sabre\DAV\Exception\Forbidden
84
-	 * @return bool
85
-	 */
86
-	public function checkAppEnabled() {
87
-		if (!$this->appManager->isEnabledForUser($this->app)) {
88
-			throw new Forbidden();
89
-		}
90
-	}
80
+    /**
81
+     * This method is called before any HTTP after auth and checks if the user has access to the app
82
+     *
83
+     * @throws \Sabre\DAV\Exception\Forbidden
84
+     * @return bool
85
+     */
86
+    public function checkAppEnabled() {
87
+        if (!$this->appManager->isEnabledForUser($this->app)) {
88
+            throw new Forbidden();
89
+        }
90
+    }
91 91
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php 1 patch
Indentation   +78 added lines, -78 removed lines patch added patch discarded remove patch
@@ -42,89 +42,89 @@
 block discarded – undo
42 42
 use Sabre\DAV\Exception\ServiceUnavailable;
43 43
 
44 44
 class ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin {
45
-	protected $nonFatalExceptions = [
46
-		NotAuthenticated::class => true,
47
-		// If tokenauth can throw this exception (which is basically as
48
-		// NotAuthenticated. So not fatal.
49
-		PasswordLoginForbidden::class => true,
50
-		// basically a NotAuthenticated
51
-		InvalidSyncToken::class => true,
52
-		// the sync client uses this to find out whether files exist,
53
-		// so it is not always an error, log it as debug
54
-		NotFound::class => true,
55
-		// this one mostly happens when the same file is uploaded at
56
-		// exactly the same time from two clients, only one client
57
-		// wins, the second one gets "Precondition failed"
58
-		PreconditionFailed::class => true,
59
-		// forbidden can be expected when trying to upload to
60
-		// read-only folders for example
61
-		Forbidden::class => true,
62
-		// Happens when an external storage or federated share is temporarily
63
-		// not available
64
-		StorageNotAvailableException::class => true,
65
-		// happens if some a client uses the wrong method for a given URL
66
-		// the error message itself is visible on the client side anyways
67
-		NotImplemented::class => true,
68
-		// happens when the parent directory is not present (for example when a
69
-		// move is done to a non-existent directory)
70
-		Conflict::class => true,
71
-		// happens when a certain method is not allowed to be called
72
-		// for example creating a folder that already exists
73
-		MethodNotAllowed::class => true,
74
-		// A locked file is perfectly valid and can happen in various cases
75
-		FileLocked::class => true,
76
-	];
45
+    protected $nonFatalExceptions = [
46
+        NotAuthenticated::class => true,
47
+        // If tokenauth can throw this exception (which is basically as
48
+        // NotAuthenticated. So not fatal.
49
+        PasswordLoginForbidden::class => true,
50
+        // basically a NotAuthenticated
51
+        InvalidSyncToken::class => true,
52
+        // the sync client uses this to find out whether files exist,
53
+        // so it is not always an error, log it as debug
54
+        NotFound::class => true,
55
+        // this one mostly happens when the same file is uploaded at
56
+        // exactly the same time from two clients, only one client
57
+        // wins, the second one gets "Precondition failed"
58
+        PreconditionFailed::class => true,
59
+        // forbidden can be expected when trying to upload to
60
+        // read-only folders for example
61
+        Forbidden::class => true,
62
+        // Happens when an external storage or federated share is temporarily
63
+        // not available
64
+        StorageNotAvailableException::class => true,
65
+        // happens if some a client uses the wrong method for a given URL
66
+        // the error message itself is visible on the client side anyways
67
+        NotImplemented::class => true,
68
+        // happens when the parent directory is not present (for example when a
69
+        // move is done to a non-existent directory)
70
+        Conflict::class => true,
71
+        // happens when a certain method is not allowed to be called
72
+        // for example creating a folder that already exists
73
+        MethodNotAllowed::class => true,
74
+        // A locked file is perfectly valid and can happen in various cases
75
+        FileLocked::class => true,
76
+    ];
77 77
 
78
-	/** @var string */
79
-	private $appName;
78
+    /** @var string */
79
+    private $appName;
80 80
 
81
-	/** @var ILogger */
82
-	private $logger;
81
+    /** @var ILogger */
82
+    private $logger;
83 83
 
84
-	/**
85
-	 * @param string $loggerAppName app name to use when logging
86
-	 * @param ILogger $logger
87
-	 */
88
-	public function __construct($loggerAppName, $logger) {
89
-		$this->appName = $loggerAppName;
90
-		$this->logger = $logger;
91
-	}
84
+    /**
85
+     * @param string $loggerAppName app name to use when logging
86
+     * @param ILogger $logger
87
+     */
88
+    public function __construct($loggerAppName, $logger) {
89
+        $this->appName = $loggerAppName;
90
+        $this->logger = $logger;
91
+    }
92 92
 
93
-	/**
94
-	 * This initializes the plugin.
95
-	 *
96
-	 * This function is called by \Sabre\DAV\Server, after
97
-	 * addPlugin is called.
98
-	 *
99
-	 * This method should set up the required event subscriptions.
100
-	 *
101
-	 * @param \Sabre\DAV\Server $server
102
-	 * @return void
103
-	 */
104
-	public function initialize(\Sabre\DAV\Server $server) {
93
+    /**
94
+     * This initializes the plugin.
95
+     *
96
+     * This function is called by \Sabre\DAV\Server, after
97
+     * addPlugin is called.
98
+     *
99
+     * This method should set up the required event subscriptions.
100
+     *
101
+     * @param \Sabre\DAV\Server $server
102
+     * @return void
103
+     */
104
+    public function initialize(\Sabre\DAV\Server $server) {
105 105
 
106
-		$server->on('exception', [$this, 'logException'], 10);
107
-	}
106
+        $server->on('exception', [$this, 'logException'], 10);
107
+    }
108 108
 
109
-	/**
110
-	 * Log exception
111
-	 *
112
-	 */
113
-	public function logException(\Exception $ex) {
114
-		$exceptionClass = get_class($ex);
115
-		$level = ILogger::FATAL;
116
-		if (isset($this->nonFatalExceptions[$exceptionClass]) ||
117
-			(
118
-				$exceptionClass === ServiceUnavailable::class &&
119
-				$ex->getMessage() === 'System in maintenance mode.'
120
-			)
121
-		) {
122
-			$level = ILogger::DEBUG;
123
-		}
109
+    /**
110
+     * Log exception
111
+     *
112
+     */
113
+    public function logException(\Exception $ex) {
114
+        $exceptionClass = get_class($ex);
115
+        $level = ILogger::FATAL;
116
+        if (isset($this->nonFatalExceptions[$exceptionClass]) ||
117
+            (
118
+                $exceptionClass === ServiceUnavailable::class &&
119
+                $ex->getMessage() === 'System in maintenance mode.'
120
+            )
121
+        ) {
122
+            $level = ILogger::DEBUG;
123
+        }
124 124
 
125
-		$this->logger->logException($ex, [
126
-			'app' => $this->appName,
127
-			'level' => $level,
128
-		]);
129
-	}
125
+        $this->logger->logException($ex, [
126
+            'app' => $this->appName,
127
+            'level' => $level,
128
+        ]);
129
+    }
130 130
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/FilesReportPlugin.php 1 patch
Indentation   +394 added lines, -394 removed lines patch added patch discarded remove patch
@@ -46,398 +46,398 @@
 block discarded – undo
46 46
 
47 47
 class FilesReportPlugin extends ServerPlugin {
48 48
 
49
-	// namespace
50
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
51
-	const REPORT_NAME            = '{http://owncloud.org/ns}filter-files';
52
-	const SYSTEMTAG_PROPERTYNAME = '{http://owncloud.org/ns}systemtag';
53
-	const CIRCLE_PROPERTYNAME = '{http://owncloud.org/ns}circle';
54
-
55
-	/**
56
-	 * Reference to main server object
57
-	 *
58
-	 * @var \Sabre\DAV\Server
59
-	 */
60
-	private $server;
61
-
62
-	/**
63
-	 * @var Tree
64
-	 */
65
-	private $tree;
66
-
67
-	/**
68
-	 * @var View
69
-	 */
70
-	private $fileView;
71
-
72
-	/**
73
-	 * @var ISystemTagManager
74
-	 */
75
-	private $tagManager;
76
-
77
-	/**
78
-	 * @var ISystemTagObjectMapper
79
-	 */
80
-	private $tagMapper;
81
-
82
-	/**
83
-	 * Manager for private tags
84
-	 *
85
-	 * @var ITagManager
86
-	 */
87
-	private $fileTagger;
88
-
89
-	/**
90
-	 * @var IUserSession
91
-	 */
92
-	private $userSession;
93
-
94
-	/**
95
-	 * @var IGroupManager
96
-	 */
97
-	private $groupManager;
98
-
99
-	/**
100
-	 * @var Folder
101
-	 */
102
-	private $userFolder;
103
-
104
-	/**
105
-	 * @var IAppManager
106
-	 */
107
-	private $appManager;
108
-
109
-	/**
110
-	 * @param Tree $tree
111
-	 * @param View $view
112
-	 * @param ISystemTagManager $tagManager
113
-	 * @param ISystemTagObjectMapper $tagMapper
114
-	 * @param ITagManager $fileTagger manager for private tags
115
-	 * @param IUserSession $userSession
116
-	 * @param IGroupManager $groupManager
117
-	 * @param Folder $userFolder
118
-	 * @param IAppManager $appManager
119
-	 */
120
-	public function __construct(Tree $tree,
121
-								View $view,
122
-								ISystemTagManager $tagManager,
123
-								ISystemTagObjectMapper $tagMapper,
124
-								ITagManager $fileTagger,
125
-								IUserSession $userSession,
126
-								IGroupManager $groupManager,
127
-								Folder $userFolder,
128
-								IAppManager $appManager
129
-	) {
130
-		$this->tree = $tree;
131
-		$this->fileView = $view;
132
-		$this->tagManager = $tagManager;
133
-		$this->tagMapper = $tagMapper;
134
-		$this->fileTagger = $fileTagger;
135
-		$this->userSession = $userSession;
136
-		$this->groupManager = $groupManager;
137
-		$this->userFolder = $userFolder;
138
-		$this->appManager = $appManager;
139
-	}
140
-
141
-	/**
142
-	 * This initializes the plugin.
143
-	 *
144
-	 * This function is called by \Sabre\DAV\Server, after
145
-	 * addPlugin is called.
146
-	 *
147
-	 * This method should set up the required event subscriptions.
148
-	 *
149
-	 * @param \Sabre\DAV\Server $server
150
-	 * @return void
151
-	 */
152
-	public function initialize(\Sabre\DAV\Server $server) {
153
-
154
-		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
155
-
156
-		$this->server = $server;
157
-		$this->server->on('report', [$this, 'onReport']);
158
-	}
159
-
160
-	/**
161
-	 * Returns a list of reports this plugin supports.
162
-	 *
163
-	 * This will be used in the {DAV:}supported-report-set property.
164
-	 *
165
-	 * @param string $uri
166
-	 * @return array
167
-	 */
168
-	public function getSupportedReportSet($uri) {
169
-		return [self::REPORT_NAME];
170
-	}
171
-
172
-	/**
173
-	 * REPORT operations to look for files
174
-	 *
175
-	 * @param string $reportName
176
-	 * @param $report
177
-	 * @param string $uri
178
-	 * @return bool
179
-	 * @throws BadRequest
180
-	 * @throws PreconditionFailed
181
-	 * @internal param $ [] $report
182
-	 */
183
-	public function onReport($reportName, $report, $uri) {
184
-		$reportTargetNode = $this->server->tree->getNodeForPath($uri);
185
-		if (!$reportTargetNode instanceof Directory || $reportName !== self::REPORT_NAME) {
186
-			return;
187
-		}
188
-
189
-		$ns = '{' . $this::NS_OWNCLOUD . '}';
190
-		$requestedProps = [];
191
-		$filterRules = [];
192
-
193
-		// parse report properties and gather filter info
194
-		foreach ($report as $reportProps) {
195
-			$name = $reportProps['name'];
196
-			if ($name === $ns . 'filter-rules') {
197
-				$filterRules = $reportProps['value'];
198
-			} else if ($name === '{DAV:}prop') {
199
-				// propfind properties
200
-				foreach ($reportProps['value'] as $propVal) {
201
-					$requestedProps[] = $propVal['name'];
202
-				}
203
-			}
204
-		}
205
-
206
-		if (empty($filterRules)) {
207
-			// an empty filter would return all existing files which would be slow
208
-			throw new BadRequest('Missing filter-rule block in request');
209
-		}
210
-
211
-		// gather all file ids matching filter
212
-		try {
213
-			$resultFileIds = $this->processFilterRules($filterRules);
214
-		} catch (TagNotFoundException $e) {
215
-			throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e);
216
-		}
217
-
218
-		// find sabre nodes by file id, restricted to the root node path
219
-		$results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds);
220
-
221
-		$filesUri = $this->getFilesBaseUri($uri, $reportTargetNode->getPath());
222
-		$responses = $this->prepareResponses($filesUri, $requestedProps, $results);
223
-
224
-		$xml = $this->server->xml->write(
225
-			'{DAV:}multistatus',
226
-			new MultiStatus($responses)
227
-		);
228
-
229
-		$this->server->httpResponse->setStatus(207);
230
-		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
231
-		$this->server->httpResponse->setBody($xml);
232
-
233
-		return false;
234
-	}
235
-
236
-	/**
237
-	 * Returns the base uri of the files root by removing
238
-	 * the subpath from the URI
239
-	 *
240
-	 * @param string $uri URI from this request
241
-	 * @param string $subPath subpath to remove from the URI
242
-	 *
243
-	 * @return string files base uri
244
-	 */
245
-	private function getFilesBaseUri($uri, $subPath) {
246
-		$uri = trim($uri, '/');
247
-		$subPath = trim($subPath, '/');
248
-		if (empty($subPath)) {
249
-			$filesUri = $uri;
250
-		} else {
251
-			$filesUri = substr($uri, 0, strlen($uri) - strlen($subPath));
252
-		}
253
-		$filesUri = trim($filesUri, '/');
254
-		if (empty($filesUri)) {
255
-			return '';
256
-		}
257
-		return '/' . $filesUri;
258
-	}
259
-
260
-	/**
261
-	 * Find file ids matching the given filter rules
262
-	 *
263
-	 * @param array $filterRules
264
-	 * @return array array of unique file id results
265
-	 *
266
-	 * @throws TagNotFoundException whenever a tag was not found
267
-	 */
268
-	protected function processFilterRules($filterRules) {
269
-		$ns = '{' . $this::NS_OWNCLOUD . '}';
270
-		$resultFileIds = null;
271
-		$systemTagIds = [];
272
-		$circlesIds = [];
273
-		$favoriteFilter = null;
274
-		foreach ($filterRules as $filterRule) {
275
-			if ($filterRule['name'] === $ns . 'systemtag') {
276
-				$systemTagIds[] = $filterRule['value'];
277
-			}
278
-			if ($filterRule['name'] === self::CIRCLE_PROPERTYNAME) {
279
-				$circlesIds[] = $filterRule['value'];
280
-			}
281
-			if ($filterRule['name'] === $ns . 'favorite') {
282
-				$favoriteFilter = true;
283
-			}
284
-
285
-		}
286
-
287
-		if ($favoriteFilter !== null) {
288
-			$resultFileIds = $this->fileTagger->load('files')->getFavorites();
289
-			if (empty($resultFileIds)) {
290
-				return [];
291
-			}
292
-		}
293
-
294
-		if (!empty($systemTagIds)) {
295
-			$fileIds = $this->getSystemTagFileIds($systemTagIds);
296
-			if (empty($resultFileIds)) {
297
-				$resultFileIds = $fileIds;
298
-			} else {
299
-				$resultFileIds = array_intersect($fileIds, $resultFileIds);
300
-			}
301
-		}
302
-
303
-		if (!empty($circlesIds)) {
304
-			$fileIds = $this->getCirclesFileIds($circlesIds);
305
-			if (empty($resultFileIds)) {
306
-				$resultFileIds = $fileIds;
307
-			} else {
308
-				$resultFileIds = array_intersect($fileIds, $resultFileIds);
309
-			}
310
-		}
311
-
312
-		return $resultFileIds;
313
-	}
314
-
315
-	private function getSystemTagFileIds($systemTagIds) {
316
-		$resultFileIds = null;
317
-
318
-		// check user permissions, if applicable
319
-		if (!$this->isAdmin()) {
320
-			// check visibility/permission
321
-			$tags = $this->tagManager->getTagsByIds($systemTagIds);
322
-			$unknownTagIds = [];
323
-			foreach ($tags as $tag) {
324
-				if (!$tag->isUserVisible()) {
325
-					$unknownTagIds[] = $tag->getId();
326
-				}
327
-			}
328
-
329
-			if (!empty($unknownTagIds)) {
330
-				throw new TagNotFoundException('Tag with ids ' . implode(', ', $unknownTagIds) . ' not found');
331
-			}
332
-		}
333
-
334
-		// fetch all file ids and intersect them
335
-		foreach ($systemTagIds as $systemTagId) {
336
-			$fileIds = $this->tagMapper->getObjectIdsForTags($systemTagId, 'files');
337
-
338
-			if (empty($fileIds)) {
339
-				// This tag has no files, nothing can ever show up
340
-				return [];
341
-			}
342
-
343
-			// first run ?
344
-			if ($resultFileIds === null) {
345
-				$resultFileIds = $fileIds;
346
-			} else {
347
-				$resultFileIds = array_intersect($resultFileIds, $fileIds);
348
-			}
349
-
350
-			if (empty($resultFileIds)) {
351
-				// Empty intersection, nothing can show up anymore
352
-				return [];
353
-			}
354
-		}
355
-		return $resultFileIds;
356
-	}
357
-
358
-	/**
359
-	 * @suppress PhanUndeclaredClassMethod
360
-	 * @param array $circlesIds
361
-	 * @return array
362
-	 */
363
-	private function getCirclesFileIds(array $circlesIds) {
364
-		if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) {
365
-			return [];
366
-		}
367
-		return \OCA\Circles\Api\v1\Circles::getFilesForCircles($circlesIds);
368
-	}
369
-
370
-
371
-	/**
372
-	 * Prepare propfind response for the given nodes
373
-	 *
374
-	 * @param string $filesUri $filesUri URI leading to root of the files URI,
375
-	 * with a leading slash but no trailing slash
376
-	 * @param string[] $requestedProps requested properties
377
-	 * @param Node[] nodes nodes for which to fetch and prepare responses
378
-	 * @return Response[]
379
-	 */
380
-	public function prepareResponses($filesUri, $requestedProps, $nodes) {
381
-		$responses = [];
382
-		foreach ($nodes as $node) {
383
-			$propFind = new PropFind($filesUri . $node->getPath(), $requestedProps);
384
-
385
-			$this->server->getPropertiesByNode($propFind, $node);
386
-			// copied from Sabre Server's getPropertiesForPath
387
-			$result = $propFind->getResultForMultiStatus();
388
-			$result['href'] = $propFind->getPath();
389
-
390
-			$resourceType = $this->server->getResourceTypeForNode($node);
391
-			if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
392
-				$result['href'] .= '/';
393
-			}
394
-
395
-			$responses[] = new Response(
396
-				rtrim($this->server->getBaseUri(), '/') . $filesUri . $node->getPath(),
397
-				$result,
398
-				200
399
-			);
400
-		}
401
-		return $responses;
402
-	}
403
-
404
-	/**
405
-	 * Find Sabre nodes by file ids
406
-	 *
407
-	 * @param Node $rootNode root node for search
408
-	 * @param array $fileIds file ids
409
-	 * @return Node[] array of Sabre nodes
410
-	 */
411
-	public function findNodesByFileIds($rootNode, $fileIds) {
412
-		$folder = $this->userFolder;
413
-		if (trim($rootNode->getPath(), '/') !== '') {
414
-			$folder = $folder->get($rootNode->getPath());
415
-		}
416
-
417
-		$results = [];
418
-		foreach ($fileIds as $fileId) {
419
-			$entry = $folder->getById($fileId);
420
-			if ($entry) {
421
-				$entry = current($entry);
422
-				if ($entry instanceof \OCP\Files\File) {
423
-					$results[] = new File($this->fileView, $entry);
424
-				} else if ($entry instanceof \OCP\Files\Folder) {
425
-					$results[] = new Directory($this->fileView, $entry);
426
-				}
427
-			}
428
-		}
429
-
430
-		return $results;
431
-	}
432
-
433
-	/**
434
-	 * Returns whether the currently logged in user is an administrator
435
-	 */
436
-	private function isAdmin() {
437
-		$user = $this->userSession->getUser();
438
-		if ($user !== null) {
439
-			return $this->groupManager->isAdmin($user->getUID());
440
-		}
441
-		return false;
442
-	}
49
+    // namespace
50
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
51
+    const REPORT_NAME            = '{http://owncloud.org/ns}filter-files';
52
+    const SYSTEMTAG_PROPERTYNAME = '{http://owncloud.org/ns}systemtag';
53
+    const CIRCLE_PROPERTYNAME = '{http://owncloud.org/ns}circle';
54
+
55
+    /**
56
+     * Reference to main server object
57
+     *
58
+     * @var \Sabre\DAV\Server
59
+     */
60
+    private $server;
61
+
62
+    /**
63
+     * @var Tree
64
+     */
65
+    private $tree;
66
+
67
+    /**
68
+     * @var View
69
+     */
70
+    private $fileView;
71
+
72
+    /**
73
+     * @var ISystemTagManager
74
+     */
75
+    private $tagManager;
76
+
77
+    /**
78
+     * @var ISystemTagObjectMapper
79
+     */
80
+    private $tagMapper;
81
+
82
+    /**
83
+     * Manager for private tags
84
+     *
85
+     * @var ITagManager
86
+     */
87
+    private $fileTagger;
88
+
89
+    /**
90
+     * @var IUserSession
91
+     */
92
+    private $userSession;
93
+
94
+    /**
95
+     * @var IGroupManager
96
+     */
97
+    private $groupManager;
98
+
99
+    /**
100
+     * @var Folder
101
+     */
102
+    private $userFolder;
103
+
104
+    /**
105
+     * @var IAppManager
106
+     */
107
+    private $appManager;
108
+
109
+    /**
110
+     * @param Tree $tree
111
+     * @param View $view
112
+     * @param ISystemTagManager $tagManager
113
+     * @param ISystemTagObjectMapper $tagMapper
114
+     * @param ITagManager $fileTagger manager for private tags
115
+     * @param IUserSession $userSession
116
+     * @param IGroupManager $groupManager
117
+     * @param Folder $userFolder
118
+     * @param IAppManager $appManager
119
+     */
120
+    public function __construct(Tree $tree,
121
+                                View $view,
122
+                                ISystemTagManager $tagManager,
123
+                                ISystemTagObjectMapper $tagMapper,
124
+                                ITagManager $fileTagger,
125
+                                IUserSession $userSession,
126
+                                IGroupManager $groupManager,
127
+                                Folder $userFolder,
128
+                                IAppManager $appManager
129
+    ) {
130
+        $this->tree = $tree;
131
+        $this->fileView = $view;
132
+        $this->tagManager = $tagManager;
133
+        $this->tagMapper = $tagMapper;
134
+        $this->fileTagger = $fileTagger;
135
+        $this->userSession = $userSession;
136
+        $this->groupManager = $groupManager;
137
+        $this->userFolder = $userFolder;
138
+        $this->appManager = $appManager;
139
+    }
140
+
141
+    /**
142
+     * This initializes the plugin.
143
+     *
144
+     * This function is called by \Sabre\DAV\Server, after
145
+     * addPlugin is called.
146
+     *
147
+     * This method should set up the required event subscriptions.
148
+     *
149
+     * @param \Sabre\DAV\Server $server
150
+     * @return void
151
+     */
152
+    public function initialize(\Sabre\DAV\Server $server) {
153
+
154
+        $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
155
+
156
+        $this->server = $server;
157
+        $this->server->on('report', [$this, 'onReport']);
158
+    }
159
+
160
+    /**
161
+     * Returns a list of reports this plugin supports.
162
+     *
163
+     * This will be used in the {DAV:}supported-report-set property.
164
+     *
165
+     * @param string $uri
166
+     * @return array
167
+     */
168
+    public function getSupportedReportSet($uri) {
169
+        return [self::REPORT_NAME];
170
+    }
171
+
172
+    /**
173
+     * REPORT operations to look for files
174
+     *
175
+     * @param string $reportName
176
+     * @param $report
177
+     * @param string $uri
178
+     * @return bool
179
+     * @throws BadRequest
180
+     * @throws PreconditionFailed
181
+     * @internal param $ [] $report
182
+     */
183
+    public function onReport($reportName, $report, $uri) {
184
+        $reportTargetNode = $this->server->tree->getNodeForPath($uri);
185
+        if (!$reportTargetNode instanceof Directory || $reportName !== self::REPORT_NAME) {
186
+            return;
187
+        }
188
+
189
+        $ns = '{' . $this::NS_OWNCLOUD . '}';
190
+        $requestedProps = [];
191
+        $filterRules = [];
192
+
193
+        // parse report properties and gather filter info
194
+        foreach ($report as $reportProps) {
195
+            $name = $reportProps['name'];
196
+            if ($name === $ns . 'filter-rules') {
197
+                $filterRules = $reportProps['value'];
198
+            } else if ($name === '{DAV:}prop') {
199
+                // propfind properties
200
+                foreach ($reportProps['value'] as $propVal) {
201
+                    $requestedProps[] = $propVal['name'];
202
+                }
203
+            }
204
+        }
205
+
206
+        if (empty($filterRules)) {
207
+            // an empty filter would return all existing files which would be slow
208
+            throw new BadRequest('Missing filter-rule block in request');
209
+        }
210
+
211
+        // gather all file ids matching filter
212
+        try {
213
+            $resultFileIds = $this->processFilterRules($filterRules);
214
+        } catch (TagNotFoundException $e) {
215
+            throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e);
216
+        }
217
+
218
+        // find sabre nodes by file id, restricted to the root node path
219
+        $results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds);
220
+
221
+        $filesUri = $this->getFilesBaseUri($uri, $reportTargetNode->getPath());
222
+        $responses = $this->prepareResponses($filesUri, $requestedProps, $results);
223
+
224
+        $xml = $this->server->xml->write(
225
+            '{DAV:}multistatus',
226
+            new MultiStatus($responses)
227
+        );
228
+
229
+        $this->server->httpResponse->setStatus(207);
230
+        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
231
+        $this->server->httpResponse->setBody($xml);
232
+
233
+        return false;
234
+    }
235
+
236
+    /**
237
+     * Returns the base uri of the files root by removing
238
+     * the subpath from the URI
239
+     *
240
+     * @param string $uri URI from this request
241
+     * @param string $subPath subpath to remove from the URI
242
+     *
243
+     * @return string files base uri
244
+     */
245
+    private function getFilesBaseUri($uri, $subPath) {
246
+        $uri = trim($uri, '/');
247
+        $subPath = trim($subPath, '/');
248
+        if (empty($subPath)) {
249
+            $filesUri = $uri;
250
+        } else {
251
+            $filesUri = substr($uri, 0, strlen($uri) - strlen($subPath));
252
+        }
253
+        $filesUri = trim($filesUri, '/');
254
+        if (empty($filesUri)) {
255
+            return '';
256
+        }
257
+        return '/' . $filesUri;
258
+    }
259
+
260
+    /**
261
+     * Find file ids matching the given filter rules
262
+     *
263
+     * @param array $filterRules
264
+     * @return array array of unique file id results
265
+     *
266
+     * @throws TagNotFoundException whenever a tag was not found
267
+     */
268
+    protected function processFilterRules($filterRules) {
269
+        $ns = '{' . $this::NS_OWNCLOUD . '}';
270
+        $resultFileIds = null;
271
+        $systemTagIds = [];
272
+        $circlesIds = [];
273
+        $favoriteFilter = null;
274
+        foreach ($filterRules as $filterRule) {
275
+            if ($filterRule['name'] === $ns . 'systemtag') {
276
+                $systemTagIds[] = $filterRule['value'];
277
+            }
278
+            if ($filterRule['name'] === self::CIRCLE_PROPERTYNAME) {
279
+                $circlesIds[] = $filterRule['value'];
280
+            }
281
+            if ($filterRule['name'] === $ns . 'favorite') {
282
+                $favoriteFilter = true;
283
+            }
284
+
285
+        }
286
+
287
+        if ($favoriteFilter !== null) {
288
+            $resultFileIds = $this->fileTagger->load('files')->getFavorites();
289
+            if (empty($resultFileIds)) {
290
+                return [];
291
+            }
292
+        }
293
+
294
+        if (!empty($systemTagIds)) {
295
+            $fileIds = $this->getSystemTagFileIds($systemTagIds);
296
+            if (empty($resultFileIds)) {
297
+                $resultFileIds = $fileIds;
298
+            } else {
299
+                $resultFileIds = array_intersect($fileIds, $resultFileIds);
300
+            }
301
+        }
302
+
303
+        if (!empty($circlesIds)) {
304
+            $fileIds = $this->getCirclesFileIds($circlesIds);
305
+            if (empty($resultFileIds)) {
306
+                $resultFileIds = $fileIds;
307
+            } else {
308
+                $resultFileIds = array_intersect($fileIds, $resultFileIds);
309
+            }
310
+        }
311
+
312
+        return $resultFileIds;
313
+    }
314
+
315
+    private function getSystemTagFileIds($systemTagIds) {
316
+        $resultFileIds = null;
317
+
318
+        // check user permissions, if applicable
319
+        if (!$this->isAdmin()) {
320
+            // check visibility/permission
321
+            $tags = $this->tagManager->getTagsByIds($systemTagIds);
322
+            $unknownTagIds = [];
323
+            foreach ($tags as $tag) {
324
+                if (!$tag->isUserVisible()) {
325
+                    $unknownTagIds[] = $tag->getId();
326
+                }
327
+            }
328
+
329
+            if (!empty($unknownTagIds)) {
330
+                throw new TagNotFoundException('Tag with ids ' . implode(', ', $unknownTagIds) . ' not found');
331
+            }
332
+        }
333
+
334
+        // fetch all file ids and intersect them
335
+        foreach ($systemTagIds as $systemTagId) {
336
+            $fileIds = $this->tagMapper->getObjectIdsForTags($systemTagId, 'files');
337
+
338
+            if (empty($fileIds)) {
339
+                // This tag has no files, nothing can ever show up
340
+                return [];
341
+            }
342
+
343
+            // first run ?
344
+            if ($resultFileIds === null) {
345
+                $resultFileIds = $fileIds;
346
+            } else {
347
+                $resultFileIds = array_intersect($resultFileIds, $fileIds);
348
+            }
349
+
350
+            if (empty($resultFileIds)) {
351
+                // Empty intersection, nothing can show up anymore
352
+                return [];
353
+            }
354
+        }
355
+        return $resultFileIds;
356
+    }
357
+
358
+    /**
359
+     * @suppress PhanUndeclaredClassMethod
360
+     * @param array $circlesIds
361
+     * @return array
362
+     */
363
+    private function getCirclesFileIds(array $circlesIds) {
364
+        if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) {
365
+            return [];
366
+        }
367
+        return \OCA\Circles\Api\v1\Circles::getFilesForCircles($circlesIds);
368
+    }
369
+
370
+
371
+    /**
372
+     * Prepare propfind response for the given nodes
373
+     *
374
+     * @param string $filesUri $filesUri URI leading to root of the files URI,
375
+     * with a leading slash but no trailing slash
376
+     * @param string[] $requestedProps requested properties
377
+     * @param Node[] nodes nodes for which to fetch and prepare responses
378
+     * @return Response[]
379
+     */
380
+    public function prepareResponses($filesUri, $requestedProps, $nodes) {
381
+        $responses = [];
382
+        foreach ($nodes as $node) {
383
+            $propFind = new PropFind($filesUri . $node->getPath(), $requestedProps);
384
+
385
+            $this->server->getPropertiesByNode($propFind, $node);
386
+            // copied from Sabre Server's getPropertiesForPath
387
+            $result = $propFind->getResultForMultiStatus();
388
+            $result['href'] = $propFind->getPath();
389
+
390
+            $resourceType = $this->server->getResourceTypeForNode($node);
391
+            if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
392
+                $result['href'] .= '/';
393
+            }
394
+
395
+            $responses[] = new Response(
396
+                rtrim($this->server->getBaseUri(), '/') . $filesUri . $node->getPath(),
397
+                $result,
398
+                200
399
+            );
400
+        }
401
+        return $responses;
402
+    }
403
+
404
+    /**
405
+     * Find Sabre nodes by file ids
406
+     *
407
+     * @param Node $rootNode root node for search
408
+     * @param array $fileIds file ids
409
+     * @return Node[] array of Sabre nodes
410
+     */
411
+    public function findNodesByFileIds($rootNode, $fileIds) {
412
+        $folder = $this->userFolder;
413
+        if (trim($rootNode->getPath(), '/') !== '') {
414
+            $folder = $folder->get($rootNode->getPath());
415
+        }
416
+
417
+        $results = [];
418
+        foreach ($fileIds as $fileId) {
419
+            $entry = $folder->getById($fileId);
420
+            if ($entry) {
421
+                $entry = current($entry);
422
+                if ($entry instanceof \OCP\Files\File) {
423
+                    $results[] = new File($this->fileView, $entry);
424
+                } else if ($entry instanceof \OCP\Files\Folder) {
425
+                    $results[] = new Directory($this->fileView, $entry);
426
+                }
427
+            }
428
+        }
429
+
430
+        return $results;
431
+    }
432
+
433
+    /**
434
+     * Returns whether the currently logged in user is an administrator
435
+     */
436
+    private function isAdmin() {
437
+        $user = $this->userSession->getUser();
438
+        if ($user !== null) {
439
+            return $this->groupManager->isAdmin($user->getUID());
440
+        }
441
+        return false;
442
+    }
443 443
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/TagsPlugin.php 1 patch
Indentation   +217 added lines, -217 removed lines patch added patch discarded remove patch
@@ -54,246 +54,246 @@
 block discarded – undo
54 54
 class TagsPlugin extends \Sabre\DAV\ServerPlugin
55 55
 {
56 56
 
57
-	// namespace
58
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
59
-	const TAGS_PROPERTYNAME = '{http://owncloud.org/ns}tags';
60
-	const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite';
61
-	const TAG_FAVORITE = '_$!<Favorite>!$_';
57
+    // namespace
58
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
59
+    const TAGS_PROPERTYNAME = '{http://owncloud.org/ns}tags';
60
+    const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite';
61
+    const TAG_FAVORITE = '_$!<Favorite>!$_';
62 62
 
63
-	/**
64
-	 * Reference to main server object
65
-	 *
66
-	 * @var \Sabre\DAV\Server
67
-	 */
68
-	private $server;
63
+    /**
64
+     * Reference to main server object
65
+     *
66
+     * @var \Sabre\DAV\Server
67
+     */
68
+    private $server;
69 69
 
70
-	/**
71
-	 * @var \OCP\ITagManager
72
-	 */
73
-	private $tagManager;
70
+    /**
71
+     * @var \OCP\ITagManager
72
+     */
73
+    private $tagManager;
74 74
 
75
-	/**
76
-	 * @var \OCP\ITags
77
-	 */
78
-	private $tagger;
75
+    /**
76
+     * @var \OCP\ITags
77
+     */
78
+    private $tagger;
79 79
 
80
-	/**
81
-	 * Array of file id to tags array
82
-	 * The null value means the cache wasn't initialized.
83
-	 *
84
-	 * @var array
85
-	 */
86
-	private $cachedTags;
80
+    /**
81
+     * Array of file id to tags array
82
+     * The null value means the cache wasn't initialized.
83
+     *
84
+     * @var array
85
+     */
86
+    private $cachedTags;
87 87
 
88
-	/**
89
-	 * @var \Sabre\DAV\Tree
90
-	 */
91
-	private $tree;
88
+    /**
89
+     * @var \Sabre\DAV\Tree
90
+     */
91
+    private $tree;
92 92
 
93
-	/**
94
-	 * @param \Sabre\DAV\Tree $tree tree
95
-	 * @param \OCP\ITagManager $tagManager tag manager
96
-	 */
97
-	public function __construct(\Sabre\DAV\Tree $tree, \OCP\ITagManager $tagManager) {
98
-		$this->tree = $tree;
99
-		$this->tagManager = $tagManager;
100
-		$this->tagger = null;
101
-		$this->cachedTags = [];
102
-	}
93
+    /**
94
+     * @param \Sabre\DAV\Tree $tree tree
95
+     * @param \OCP\ITagManager $tagManager tag manager
96
+     */
97
+    public function __construct(\Sabre\DAV\Tree $tree, \OCP\ITagManager $tagManager) {
98
+        $this->tree = $tree;
99
+        $this->tagManager = $tagManager;
100
+        $this->tagger = null;
101
+        $this->cachedTags = [];
102
+    }
103 103
 
104
-	/**
105
-	 * This initializes the plugin.
106
-	 *
107
-	 * This function is called by \Sabre\DAV\Server, after
108
-	 * addPlugin is called.
109
-	 *
110
-	 * This method should set up the required event subscriptions.
111
-	 *
112
-	 * @param \Sabre\DAV\Server $server
113
-	 * @return void
114
-	 */
115
-	public function initialize(\Sabre\DAV\Server $server) {
104
+    /**
105
+     * This initializes the plugin.
106
+     *
107
+     * This function is called by \Sabre\DAV\Server, after
108
+     * addPlugin is called.
109
+     *
110
+     * This method should set up the required event subscriptions.
111
+     *
112
+     * @param \Sabre\DAV\Server $server
113
+     * @return void
114
+     */
115
+    public function initialize(\Sabre\DAV\Server $server) {
116 116
 
117
-		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
118
-		$server->xml->elementMap[self::TAGS_PROPERTYNAME] = TagList::class;
117
+        $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
118
+        $server->xml->elementMap[self::TAGS_PROPERTYNAME] = TagList::class;
119 119
 
120
-		$this->server = $server;
121
-		$this->server->on('propFind', [$this, 'handleGetProperties']);
122
-		$this->server->on('propPatch', [$this, 'handleUpdateProperties']);
123
-	}
120
+        $this->server = $server;
121
+        $this->server->on('propFind', [$this, 'handleGetProperties']);
122
+        $this->server->on('propPatch', [$this, 'handleUpdateProperties']);
123
+    }
124 124
 
125
-	/**
126
-	 * Returns the tagger
127
-	 *
128
-	 * @return \OCP\ITags tagger
129
-	 */
130
-	private function getTagger() {
131
-		if (!$this->tagger) {
132
-			$this->tagger = $this->tagManager->load('files');
133
-		}
134
-		return $this->tagger;
135
-	}
125
+    /**
126
+     * Returns the tagger
127
+     *
128
+     * @return \OCP\ITags tagger
129
+     */
130
+    private function getTagger() {
131
+        if (!$this->tagger) {
132
+            $this->tagger = $this->tagManager->load('files');
133
+        }
134
+        return $this->tagger;
135
+    }
136 136
 
137
-	/**
138
-	 * Returns tags and favorites.
139
-	 *
140
-	 * @param integer $fileId file id
141
-	 * @return array list($tags, $favorite) with $tags as tag array
142
-	 * and $favorite is a boolean whether the file was favorited
143
-	 */
144
-	private function getTagsAndFav($fileId) {
145
-		$isFav = false;
146
-		$tags = $this->getTags($fileId);
147
-		if ($tags) {
148
-			$favPos = array_search(self::TAG_FAVORITE, $tags);
149
-			if ($favPos !== false) {
150
-				$isFav = true;
151
-				unset($tags[$favPos]);
152
-			}
153
-		}
154
-		return [$tags, $isFav];
155
-	}
137
+    /**
138
+     * Returns tags and favorites.
139
+     *
140
+     * @param integer $fileId file id
141
+     * @return array list($tags, $favorite) with $tags as tag array
142
+     * and $favorite is a boolean whether the file was favorited
143
+     */
144
+    private function getTagsAndFav($fileId) {
145
+        $isFav = false;
146
+        $tags = $this->getTags($fileId);
147
+        if ($tags) {
148
+            $favPos = array_search(self::TAG_FAVORITE, $tags);
149
+            if ($favPos !== false) {
150
+                $isFav = true;
151
+                unset($tags[$favPos]);
152
+            }
153
+        }
154
+        return [$tags, $isFav];
155
+    }
156 156
 
157
-	/**
158
-	 * Returns tags for the given file id
159
-	 *
160
-	 * @param integer $fileId file id
161
-	 * @return array list of tags for that file
162
-	 */
163
-	private function getTags($fileId) {
164
-		if (isset($this->cachedTags[$fileId])) {
165
-			return $this->cachedTags[$fileId];
166
-		} else {
167
-			$tags = $this->getTagger()->getTagsForObjects([$fileId]);
168
-			if ($tags !== false) {
169
-				if (empty($tags)) {
170
-					return [];
171
-				}
172
-				return current($tags);
173
-			}
174
-		}
175
-		return null;
176
-	}
157
+    /**
158
+     * Returns tags for the given file id
159
+     *
160
+     * @param integer $fileId file id
161
+     * @return array list of tags for that file
162
+     */
163
+    private function getTags($fileId) {
164
+        if (isset($this->cachedTags[$fileId])) {
165
+            return $this->cachedTags[$fileId];
166
+        } else {
167
+            $tags = $this->getTagger()->getTagsForObjects([$fileId]);
168
+            if ($tags !== false) {
169
+                if (empty($tags)) {
170
+                    return [];
171
+                }
172
+                return current($tags);
173
+            }
174
+        }
175
+        return null;
176
+    }
177 177
 
178
-	/**
179
-	 * Updates the tags of the given file id
180
-	 *
181
-	 * @param int $fileId
182
-	 * @param array $tags array of tag strings
183
-	 */
184
-	private function updateTags($fileId, $tags) {
185
-		$tagger = $this->getTagger();
186
-		$currentTags = $this->getTags($fileId);
178
+    /**
179
+     * Updates the tags of the given file id
180
+     *
181
+     * @param int $fileId
182
+     * @param array $tags array of tag strings
183
+     */
184
+    private function updateTags($fileId, $tags) {
185
+        $tagger = $this->getTagger();
186
+        $currentTags = $this->getTags($fileId);
187 187
 
188
-		$newTags = array_diff($tags, $currentTags);
189
-		foreach ($newTags as $tag) {
190
-			if ($tag === self::TAG_FAVORITE) {
191
-				continue;
192
-			}
193
-			$tagger->tagAs($fileId, $tag);
194
-		}
195
-		$deletedTags = array_diff($currentTags, $tags);
196
-		foreach ($deletedTags as $tag) {
197
-			if ($tag === self::TAG_FAVORITE) {
198
-				continue;
199
-			}
200
-			$tagger->unTag($fileId, $tag);
201
-		}
202
-	}
188
+        $newTags = array_diff($tags, $currentTags);
189
+        foreach ($newTags as $tag) {
190
+            if ($tag === self::TAG_FAVORITE) {
191
+                continue;
192
+            }
193
+            $tagger->tagAs($fileId, $tag);
194
+        }
195
+        $deletedTags = array_diff($currentTags, $tags);
196
+        foreach ($deletedTags as $tag) {
197
+            if ($tag === self::TAG_FAVORITE) {
198
+                continue;
199
+            }
200
+            $tagger->unTag($fileId, $tag);
201
+        }
202
+    }
203 203
 
204
-	/**
205
-	 * Adds tags and favorites properties to the response,
206
-	 * if requested.
207
-	 *
208
-	 * @param PropFind $propFind
209
-	 * @param \Sabre\DAV\INode $node
210
-	 * @return void
211
-	 */
212
-	public function handleGetProperties(
213
-		PropFind $propFind,
214
-		\Sabre\DAV\INode $node
215
-	) {
216
-		if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
217
-			return;
218
-		}
204
+    /**
205
+     * Adds tags and favorites properties to the response,
206
+     * if requested.
207
+     *
208
+     * @param PropFind $propFind
209
+     * @param \Sabre\DAV\INode $node
210
+     * @return void
211
+     */
212
+    public function handleGetProperties(
213
+        PropFind $propFind,
214
+        \Sabre\DAV\INode $node
215
+    ) {
216
+        if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
217
+            return;
218
+        }
219 219
 
220
-		// need prefetch ?
221
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Directory
222
-			&& $propFind->getDepth() !== 0
223
-			&& (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME))
224
-			|| !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME))
225
-		)) {
226
-			// note: pre-fetching only supported for depth <= 1
227
-			$folderContent = $node->getChildren();
228
-			$fileIds[] = (int)$node->getId();
229
-			foreach ($folderContent as $info) {
230
-				$fileIds[] = (int)$info->getId();
231
-			}
232
-			$tags = $this->getTagger()->getTagsForObjects($fileIds);
233
-			if ($tags === false) {
234
-				// the tags API returns false on error...
235
-				$tags = [];
236
-			}
220
+        // need prefetch ?
221
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Directory
222
+            && $propFind->getDepth() !== 0
223
+            && (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME))
224
+            || !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME))
225
+        )) {
226
+            // note: pre-fetching only supported for depth <= 1
227
+            $folderContent = $node->getChildren();
228
+            $fileIds[] = (int)$node->getId();
229
+            foreach ($folderContent as $info) {
230
+                $fileIds[] = (int)$info->getId();
231
+            }
232
+            $tags = $this->getTagger()->getTagsForObjects($fileIds);
233
+            if ($tags === false) {
234
+                // the tags API returns false on error...
235
+                $tags = [];
236
+            }
237 237
 
238
-			$this->cachedTags = $this->cachedTags + $tags;
239
-			$emptyFileIds = array_diff($fileIds, array_keys($tags));
240
-			// also cache the ones that were not found
241
-			foreach ($emptyFileIds as $fileId) {
242
-				$this->cachedTags[$fileId] = [];
243
-			}
244
-		}
238
+            $this->cachedTags = $this->cachedTags + $tags;
239
+            $emptyFileIds = array_diff($fileIds, array_keys($tags));
240
+            // also cache the ones that were not found
241
+            foreach ($emptyFileIds as $fileId) {
242
+                $this->cachedTags[$fileId] = [];
243
+            }
244
+        }
245 245
 
246
-		$isFav = null;
246
+        $isFav = null;
247 247
 
248
-		$propFind->handle(self::TAGS_PROPERTYNAME, function() use (&$isFav, $node) {
249
-			list($tags, $isFav) = $this->getTagsAndFav($node->getId());
250
-			return new TagList($tags);
251
-		});
248
+        $propFind->handle(self::TAGS_PROPERTYNAME, function() use (&$isFav, $node) {
249
+            list($tags, $isFav) = $this->getTagsAndFav($node->getId());
250
+            return new TagList($tags);
251
+        });
252 252
 
253
-		$propFind->handle(self::FAVORITE_PROPERTYNAME, function() use ($isFav, $node) {
254
-			if (is_null($isFav)) {
255
-				list(, $isFav) = $this->getTagsAndFav($node->getId());
256
-			}
257
-			if ($isFav) {
258
-				return 1;
259
-			} else {
260
-				return 0;
261
-			}
262
-		});
263
-	}
253
+        $propFind->handle(self::FAVORITE_PROPERTYNAME, function() use ($isFav, $node) {
254
+            if (is_null($isFav)) {
255
+                list(, $isFav) = $this->getTagsAndFav($node->getId());
256
+            }
257
+            if ($isFav) {
258
+                return 1;
259
+            } else {
260
+                return 0;
261
+            }
262
+        });
263
+    }
264 264
 
265
-	/**
266
-	 * Updates tags and favorites properties, if applicable.
267
-	 *
268
-	 * @param string $path
269
-	 * @param PropPatch $propPatch
270
-	 *
271
-	 * @return void
272
-	 */
273
-	public function handleUpdateProperties($path, PropPatch $propPatch) {
274
-		$node = $this->tree->getNodeForPath($path);
275
-		if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
276
-			return;
277
-		}
265
+    /**
266
+     * Updates tags and favorites properties, if applicable.
267
+     *
268
+     * @param string $path
269
+     * @param PropPatch $propPatch
270
+     *
271
+     * @return void
272
+     */
273
+    public function handleUpdateProperties($path, PropPatch $propPatch) {
274
+        $node = $this->tree->getNodeForPath($path);
275
+        if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
276
+            return;
277
+        }
278 278
 
279
-		$propPatch->handle(self::TAGS_PROPERTYNAME, function($tagList) use ($node) {
280
-			$this->updateTags($node->getId(), $tagList->getTags());
281
-			return true;
282
-		});
279
+        $propPatch->handle(self::TAGS_PROPERTYNAME, function($tagList) use ($node) {
280
+            $this->updateTags($node->getId(), $tagList->getTags());
281
+            return true;
282
+        });
283 283
 
284
-		$propPatch->handle(self::FAVORITE_PROPERTYNAME, function($favState) use ($node) {
285
-			if ((int)$favState === 1 || $favState === 'true') {
286
-				$this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE);
287
-			} else {
288
-				$this->getTagger()->unTag($node->getId(), self::TAG_FAVORITE);
289
-			}
284
+        $propPatch->handle(self::FAVORITE_PROPERTYNAME, function($favState) use ($node) {
285
+            if ((int)$favState === 1 || $favState === 'true') {
286
+                $this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE);
287
+            } else {
288
+                $this->getTagger()->unTag($node->getId(), self::TAG_FAVORITE);
289
+            }
290 290
 
291
-			if (is_null($favState)) {
292
-				// confirm deletion
293
-				return 204;
294
-			}
291
+            if (is_null($favState)) {
292
+                // confirm deletion
293
+                return 204;
294
+            }
295 295
 
296
-			return 200;
297
-		});
298
-	}
296
+            return 200;
297
+        });
298
+    }
299 299
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/SharesPlugin.php 1 patch
Indentation   +177 added lines, -177 removed lines patch added patch discarded remove patch
@@ -35,181 +35,181 @@
 block discarded – undo
35 35
  */
36 36
 class SharesPlugin extends \Sabre\DAV\ServerPlugin {
37 37
 
38
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
39
-	const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
40
-	const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types';
41
-	const SHAREES_PROPERTYNAME = '{http://nextcloud.org/ns}sharees';
42
-
43
-	/**
44
-	 * Reference to main server object
45
-	 *
46
-	 * @var \Sabre\DAV\Server
47
-	 */
48
-	private $server;
49
-
50
-	/**
51
-	 * @var \OCP\Share\IManager
52
-	 */
53
-	private $shareManager;
54
-
55
-	/**
56
-	 * @var \Sabre\DAV\Tree
57
-	 */
58
-	private $tree;
59
-
60
-	/**
61
-	 * @var string
62
-	 */
63
-	private $userId;
64
-
65
-	/**
66
-	 * @var \OCP\Files\Folder
67
-	 */
68
-	private $userFolder;
69
-
70
-	/** @var IShare[] */
71
-	private $cachedShares = [];
72
-
73
-	private $cachedFolders = [];
74
-
75
-	/**
76
-	 * @param \Sabre\DAV\Tree $tree tree
77
-	 * @param IUserSession $userSession user session
78
-	 * @param \OCP\Files\Folder $userFolder user home folder
79
-	 * @param \OCP\Share\IManager $shareManager share manager
80
-	 */
81
-	public function __construct(
82
-		\Sabre\DAV\Tree $tree,
83
-		IUserSession $userSession,
84
-		\OCP\Files\Folder $userFolder,
85
-		\OCP\Share\IManager $shareManager
86
-	) {
87
-		$this->tree = $tree;
88
-		$this->shareManager = $shareManager;
89
-		$this->userFolder = $userFolder;
90
-		$this->userId = $userSession->getUser()->getUID();
91
-	}
92
-
93
-	/**
94
-	 * This initializes the plugin.
95
-	 *
96
-	 * This function is called by \Sabre\DAV\Server, after
97
-	 * addPlugin is called.
98
-	 *
99
-	 * This method should set up the required event subscriptions.
100
-	 *
101
-	 * @param \Sabre\DAV\Server $server
102
-	 */
103
-	public function initialize(\Sabre\DAV\Server $server) {
104
-		$server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc';
105
-		$server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = ShareTypeList::class;
106
-		$server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME;
107
-		$server->protectedProperties[] = self::SHAREES_PROPERTYNAME;
108
-
109
-		$this->server = $server;
110
-		$this->server->on('propFind', [$this, 'handleGetProperties']);
111
-	}
112
-
113
-	private function getShare(\OCP\Files\Node $node): array {
114
-		$result = [];
115
-		$requestedShareTypes = [
116
-			\OCP\Share::SHARE_TYPE_USER,
117
-			\OCP\Share::SHARE_TYPE_GROUP,
118
-			\OCP\Share::SHARE_TYPE_LINK,
119
-			\OCP\Share::SHARE_TYPE_REMOTE,
120
-			\OCP\Share::SHARE_TYPE_EMAIL,
121
-			\OCP\Share::SHARE_TYPE_ROOM,
122
-			\OCP\Share::SHARE_TYPE_CIRCLE,
123
-		];
124
-		foreach ($requestedShareTypes as $requestedShareType) {
125
-			$shares = $this->shareManager->getSharesBy(
126
-				$this->userId,
127
-				$requestedShareType,
128
-				$node,
129
-				false,
130
-				-1
131
-			);
132
-			foreach ($shares as $share) {
133
-				$result[] = $share;
134
-			}
135
-		}
136
-		return $result;
137
-	}
138
-
139
-	private function getSharesFolder(\OCP\Files\Folder $node): array {
140
-		return $this->shareManager->getSharesInFolder(
141
-			$this->userId,
142
-			$node,
143
-			true
144
-		);
145
-	}
146
-
147
-	private function getShares(\Sabre\DAV\INode $sabreNode): array {
148
-		if (isset($this->cachedShares[$sabreNode->getId()])) {
149
-			$shares = $this->cachedShares[$sabreNode->getId()];
150
-		} else {
151
-			list($parentPath,) = \Sabre\Uri\split($sabreNode->getPath());
152
-			if ($parentPath === '') {
153
-				$parentPath = '/';
154
-			}
155
-			// if we already cached the folder this file is in we know there are no shares for this file
156
-			if (array_search($parentPath, $this->cachedFolders) === false) {
157
-				$node = $this->userFolder->get($sabreNode->getPath());
158
-				$shares = $this->getShare($node);
159
-				$this->cachedShares[$sabreNode->getId()] = $shares;
160
-			} else {
161
-				return [];
162
-			}
163
-		}
164
-
165
-		return $shares;
166
-	}
167
-
168
-	/**
169
-	 * Adds shares to propfind response
170
-	 *
171
-	 * @param PropFind $propFind propfind object
172
-	 * @param \Sabre\DAV\INode $sabreNode sabre node
173
-	 */
174
-	public function handleGetProperties(
175
-		PropFind $propFind,
176
-		\Sabre\DAV\INode $sabreNode
177
-	) {
178
-		if (!($sabreNode instanceof \OCA\DAV\Connector\Sabre\Node)) {
179
-			return;
180
-		}
181
-
182
-		// need prefetch ?
183
-		if ($sabreNode instanceof \OCA\DAV\Connector\Sabre\Directory
184
-			&& $propFind->getDepth() !== 0
185
-			&& (
186
-				!is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME)) ||
187
-				!is_null($propFind->getStatus(self::SHAREES_PROPERTYNAME))
188
-			)
189
-		) {
190
-			$folderNode = $this->userFolder->get($sabreNode->getPath());
191
-
192
-			$this->cachedFolders[] = $sabreNode->getPath();
193
-			$childShares = $this->getSharesFolder($folderNode);
194
-			foreach ($childShares as $id => $shares) {
195
-				$this->cachedShares[$id] = $shares;
196
-			}
197
-		}
198
-
199
-		$propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode) {
200
-			$shares = $this->getShares($sabreNode);
201
-
202
-			$shareTypes = array_unique(array_map(function(IShare $share) {
203
-				return $share->getShareType();
204
-			}, $shares));
205
-
206
-			return new ShareTypeList($shareTypes);
207
-		});
208
-
209
-		$propFind->handle(self::SHAREES_PROPERTYNAME, function() use ($sabreNode) {
210
-			$shares = $this->getShares($sabreNode);
211
-
212
-			return new ShareeList($shares);
213
-		});
214
-	}
38
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
39
+    const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
40
+    const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types';
41
+    const SHAREES_PROPERTYNAME = '{http://nextcloud.org/ns}sharees';
42
+
43
+    /**
44
+     * Reference to main server object
45
+     *
46
+     * @var \Sabre\DAV\Server
47
+     */
48
+    private $server;
49
+
50
+    /**
51
+     * @var \OCP\Share\IManager
52
+     */
53
+    private $shareManager;
54
+
55
+    /**
56
+     * @var \Sabre\DAV\Tree
57
+     */
58
+    private $tree;
59
+
60
+    /**
61
+     * @var string
62
+     */
63
+    private $userId;
64
+
65
+    /**
66
+     * @var \OCP\Files\Folder
67
+     */
68
+    private $userFolder;
69
+
70
+    /** @var IShare[] */
71
+    private $cachedShares = [];
72
+
73
+    private $cachedFolders = [];
74
+
75
+    /**
76
+     * @param \Sabre\DAV\Tree $tree tree
77
+     * @param IUserSession $userSession user session
78
+     * @param \OCP\Files\Folder $userFolder user home folder
79
+     * @param \OCP\Share\IManager $shareManager share manager
80
+     */
81
+    public function __construct(
82
+        \Sabre\DAV\Tree $tree,
83
+        IUserSession $userSession,
84
+        \OCP\Files\Folder $userFolder,
85
+        \OCP\Share\IManager $shareManager
86
+    ) {
87
+        $this->tree = $tree;
88
+        $this->shareManager = $shareManager;
89
+        $this->userFolder = $userFolder;
90
+        $this->userId = $userSession->getUser()->getUID();
91
+    }
92
+
93
+    /**
94
+     * This initializes the plugin.
95
+     *
96
+     * This function is called by \Sabre\DAV\Server, after
97
+     * addPlugin is called.
98
+     *
99
+     * This method should set up the required event subscriptions.
100
+     *
101
+     * @param \Sabre\DAV\Server $server
102
+     */
103
+    public function initialize(\Sabre\DAV\Server $server) {
104
+        $server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc';
105
+        $server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = ShareTypeList::class;
106
+        $server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME;
107
+        $server->protectedProperties[] = self::SHAREES_PROPERTYNAME;
108
+
109
+        $this->server = $server;
110
+        $this->server->on('propFind', [$this, 'handleGetProperties']);
111
+    }
112
+
113
+    private function getShare(\OCP\Files\Node $node): array {
114
+        $result = [];
115
+        $requestedShareTypes = [
116
+            \OCP\Share::SHARE_TYPE_USER,
117
+            \OCP\Share::SHARE_TYPE_GROUP,
118
+            \OCP\Share::SHARE_TYPE_LINK,
119
+            \OCP\Share::SHARE_TYPE_REMOTE,
120
+            \OCP\Share::SHARE_TYPE_EMAIL,
121
+            \OCP\Share::SHARE_TYPE_ROOM,
122
+            \OCP\Share::SHARE_TYPE_CIRCLE,
123
+        ];
124
+        foreach ($requestedShareTypes as $requestedShareType) {
125
+            $shares = $this->shareManager->getSharesBy(
126
+                $this->userId,
127
+                $requestedShareType,
128
+                $node,
129
+                false,
130
+                -1
131
+            );
132
+            foreach ($shares as $share) {
133
+                $result[] = $share;
134
+            }
135
+        }
136
+        return $result;
137
+    }
138
+
139
+    private function getSharesFolder(\OCP\Files\Folder $node): array {
140
+        return $this->shareManager->getSharesInFolder(
141
+            $this->userId,
142
+            $node,
143
+            true
144
+        );
145
+    }
146
+
147
+    private function getShares(\Sabre\DAV\INode $sabreNode): array {
148
+        if (isset($this->cachedShares[$sabreNode->getId()])) {
149
+            $shares = $this->cachedShares[$sabreNode->getId()];
150
+        } else {
151
+            list($parentPath,) = \Sabre\Uri\split($sabreNode->getPath());
152
+            if ($parentPath === '') {
153
+                $parentPath = '/';
154
+            }
155
+            // if we already cached the folder this file is in we know there are no shares for this file
156
+            if (array_search($parentPath, $this->cachedFolders) === false) {
157
+                $node = $this->userFolder->get($sabreNode->getPath());
158
+                $shares = $this->getShare($node);
159
+                $this->cachedShares[$sabreNode->getId()] = $shares;
160
+            } else {
161
+                return [];
162
+            }
163
+        }
164
+
165
+        return $shares;
166
+    }
167
+
168
+    /**
169
+     * Adds shares to propfind response
170
+     *
171
+     * @param PropFind $propFind propfind object
172
+     * @param \Sabre\DAV\INode $sabreNode sabre node
173
+     */
174
+    public function handleGetProperties(
175
+        PropFind $propFind,
176
+        \Sabre\DAV\INode $sabreNode
177
+    ) {
178
+        if (!($sabreNode instanceof \OCA\DAV\Connector\Sabre\Node)) {
179
+            return;
180
+        }
181
+
182
+        // need prefetch ?
183
+        if ($sabreNode instanceof \OCA\DAV\Connector\Sabre\Directory
184
+            && $propFind->getDepth() !== 0
185
+            && (
186
+                !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME)) ||
187
+                !is_null($propFind->getStatus(self::SHAREES_PROPERTYNAME))
188
+            )
189
+        ) {
190
+            $folderNode = $this->userFolder->get($sabreNode->getPath());
191
+
192
+            $this->cachedFolders[] = $sabreNode->getPath();
193
+            $childShares = $this->getSharesFolder($folderNode);
194
+            foreach ($childShares as $id => $shares) {
195
+                $this->cachedShares[$id] = $shares;
196
+            }
197
+        }
198
+
199
+        $propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode) {
200
+            $shares = $this->getShares($sabreNode);
201
+
202
+            $shareTypes = array_unique(array_map(function(IShare $share) {
203
+                return $share->getShareType();
204
+            }, $shares));
205
+
206
+            return new ShareTypeList($shareTypes);
207
+        });
208
+
209
+        $propFind->handle(self::SHAREES_PROPERTYNAME, function() use ($sabreNode) {
210
+            $shares = $this->getShares($sabreNode);
211
+
212
+            return new ShareeList($shares);
213
+        });
214
+    }
215 215
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/Directory.php 1 patch
Indentation   +401 added lines, -401 removed lines patch added patch discarded remove patch
@@ -50,405 +50,405 @@
 block discarded – undo
50 50
 use Sabre\DAV\INode;
51 51
 
52 52
 class Directory extends \OCA\DAV\Connector\Sabre\Node
53
-	implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota, \Sabre\DAV\IMoveTarget {
54
-
55
-	/**
56
-	 * Cached directory content
57
-	 *
58
-	 * @var \OCP\Files\FileInfo[]
59
-	 */
60
-	private $dirContent;
61
-
62
-	/**
63
-	 * Cached quota info
64
-	 *
65
-	 * @var array
66
-	 */
67
-	private $quotaInfo;
68
-
69
-	/**
70
-	 * @var ObjectTree|null
71
-	 */
72
-	private $tree;
73
-
74
-	/**
75
-	 * Sets up the node, expects a full path name
76
-	 *
77
-	 * @param \OC\Files\View $view
78
-	 * @param \OCP\Files\FileInfo $info
79
-	 * @param ObjectTree|null $tree
80
-	 * @param \OCP\Share\IManager $shareManager
81
-	 */
82
-	public function __construct(View $view, FileInfo $info, $tree = null, $shareManager = null) {
83
-		parent::__construct($view, $info, $shareManager);
84
-		$this->tree = $tree;
85
-	}
86
-
87
-	/**
88
-	 * Creates a new file in the directory
89
-	 *
90
-	 * Data will either be supplied as a stream resource, or in certain cases
91
-	 * as a string. Keep in mind that you may have to support either.
92
-	 *
93
-	 * After successful creation of the file, you may choose to return the ETag
94
-	 * of the new file here.
95
-	 *
96
-	 * The returned ETag must be surrounded by double-quotes (The quotes should
97
-	 * be part of the actual string).
98
-	 *
99
-	 * If you cannot accurately determine the ETag, you should not return it.
100
-	 * If you don't store the file exactly as-is (you're transforming it
101
-	 * somehow) you should also not return an ETag.
102
-	 *
103
-	 * This means that if a subsequent GET to this new file does not exactly
104
-	 * return the same contents of what was submitted here, you are strongly
105
-	 * recommended to omit the ETag.
106
-	 *
107
-	 * @param string $name Name of the file
108
-	 * @param resource|string $data Initial payload
109
-	 * @return null|string
110
-	 * @throws Exception\EntityTooLarge
111
-	 * @throws Exception\UnsupportedMediaType
112
-	 * @throws FileLocked
113
-	 * @throws InvalidPath
114
-	 * @throws \Sabre\DAV\Exception
115
-	 * @throws \Sabre\DAV\Exception\BadRequest
116
-	 * @throws \Sabre\DAV\Exception\Forbidden
117
-	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
118
-	 */
119
-	public function createFile($name, $data = null) {
120
-
121
-		try {
122
-			// for chunked upload also updating a existing file is a "createFile"
123
-			// because we create all the chunks before re-assemble them to the existing file.
124
-			if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
125
-
126
-				// exit if we can't create a new file and we don't updatable existing file
127
-				$chunkInfo = \OC_FileChunking::decodeName($name);
128
-				if (!$this->fileView->isCreatable($this->path) &&
129
-					!$this->fileView->isUpdatable($this->path . '/' . $chunkInfo['name'])
130
-				) {
131
-					throw new \Sabre\DAV\Exception\Forbidden();
132
-				}
133
-
134
-			} else {
135
-				// For non-chunked upload it is enough to check if we can create a new file
136
-				if (!$this->fileView->isCreatable($this->path)) {
137
-					throw new \Sabre\DAV\Exception\Forbidden();
138
-				}
139
-			}
140
-
141
-			$this->fileView->verifyPath($this->path, $name);
142
-
143
-			$path = $this->fileView->getAbsolutePath($this->path) . '/' . $name;
144
-			// in case the file already exists/overwriting
145
-			$info = $this->fileView->getFileInfo($this->path . '/' . $name);
146
-			if (!$info) {
147
-				// use a dummy FileInfo which is acceptable here since it will be refreshed after the put is complete
148
-				$info = new \OC\Files\FileInfo($path, null, null, [], null);
149
-			}
150
-			$node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info);
151
-
152
-			// only allow 1 process to upload a file at once but still allow reading the file while writing the part file
153
-			$node->acquireLock(ILockingProvider::LOCK_SHARED);
154
-			$this->fileView->lockFile($path . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
155
-
156
-			$result = $node->put($data);
157
-
158
-			$this->fileView->unlockFile($path . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
159
-			$node->releaseLock(ILockingProvider::LOCK_SHARED);
160
-			return $result;
161
-		} catch (\OCP\Files\StorageNotAvailableException $e) {
162
-			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), $e->getCode(), $e);
163
-		} catch (InvalidPathException $ex) {
164
-			throw new InvalidPath($ex->getMessage(), false, $ex);
165
-		} catch (ForbiddenException $ex) {
166
-			throw new Forbidden($ex->getMessage(), $ex->getRetry(), $ex);
167
-		} catch (LockedException $e) {
168
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
169
-		}
170
-	}
171
-
172
-	/**
173
-	 * Creates a new subdirectory
174
-	 *
175
-	 * @param string $name
176
-	 * @throws FileLocked
177
-	 * @throws InvalidPath
178
-	 * @throws \Sabre\DAV\Exception\Forbidden
179
-	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
180
-	 */
181
-	public function createDirectory($name) {
182
-		try {
183
-			if (!$this->info->isCreatable()) {
184
-				throw new \Sabre\DAV\Exception\Forbidden();
185
-			}
186
-
187
-			$this->fileView->verifyPath($this->path, $name);
188
-			$newPath = $this->path . '/' . $name;
189
-			if (!$this->fileView->mkdir($newPath)) {
190
-				throw new \Sabre\DAV\Exception\Forbidden('Could not create directory ' . $newPath);
191
-			}
192
-		} catch (\OCP\Files\StorageNotAvailableException $e) {
193
-			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
194
-		} catch (InvalidPathException $ex) {
195
-			throw new InvalidPath($ex->getMessage());
196
-		} catch (ForbiddenException $ex) {
197
-			throw new Forbidden($ex->getMessage(), $ex->getRetry());
198
-		} catch (LockedException $e) {
199
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
200
-		}
201
-	}
202
-
203
-	/**
204
-	 * Returns a specific child node, referenced by its name
205
-	 *
206
-	 * @param string $name
207
-	 * @param \OCP\Files\FileInfo $info
208
-	 * @return \Sabre\DAV\INode
209
-	 * @throws InvalidPath
210
-	 * @throws \Sabre\DAV\Exception\NotFound
211
-	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
212
-	 */
213
-	public function getChild($name, $info = null) {
214
-		if (!$this->info->isReadable()) {
215
-			// avoid detecting files through this way
216
-			throw new NotFound();
217
-		}
218
-
219
-		$path = $this->path . '/' . $name;
220
-		if (is_null($info)) {
221
-			try {
222
-				$this->fileView->verifyPath($this->path, $name);
223
-				$info = $this->fileView->getFileInfo($path);
224
-			} catch (\OCP\Files\StorageNotAvailableException $e) {
225
-				throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
226
-			} catch (InvalidPathException $ex) {
227
-				throw new InvalidPath($ex->getMessage());
228
-			} catch (ForbiddenException $e) {
229
-				throw new \Sabre\DAV\Exception\Forbidden();
230
-			}
231
-		}
232
-
233
-		if (!$info) {
234
-			throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located');
235
-		}
236
-
237
-		if ($info['mimetype'] === 'httpd/unix-directory') {
238
-			$node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this->tree, $this->shareManager);
239
-		} else {
240
-			$node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info, $this->shareManager);
241
-		}
242
-		if ($this->tree) {
243
-			$this->tree->cacheNode($node);
244
-		}
245
-		return $node;
246
-	}
247
-
248
-	/**
249
-	 * Returns an array with all the child nodes
250
-	 *
251
-	 * @return \Sabre\DAV\INode[]
252
-	 * @throws \Sabre\DAV\Exception\Locked
253
-	 * @throws \OCA\DAV\Connector\Sabre\Exception\Forbidden
254
-	 */
255
-	public function getChildren() {
256
-		if (!is_null($this->dirContent)) {
257
-			return $this->dirContent;
258
-		}
259
-		try {
260
-			if (!$this->info->isReadable()) {
261
-				// return 403 instead of 404 because a 404 would make
262
-				// the caller believe that the collection itself does not exist
263
-				throw new Forbidden('No read permissions');
264
-			}
265
-			$folderContent = $this->fileView->getDirectoryContent($this->path);
266
-		} catch (LockedException $e) {
267
-			throw new Locked();
268
-		}
269
-
270
-		$nodes = [];
271
-		foreach ($folderContent as $info) {
272
-			$node = $this->getChild($info->getName(), $info);
273
-			$nodes[] = $node;
274
-		}
275
-		$this->dirContent = $nodes;
276
-		return $this->dirContent;
277
-	}
278
-
279
-	/**
280
-	 * Checks if a child exists.
281
-	 *
282
-	 * @param string $name
283
-	 * @return bool
284
-	 */
285
-	public function childExists($name) {
286
-		// note: here we do NOT resolve the chunk file name to the real file name
287
-		// to make sure we return false when checking for file existence with a chunk
288
-		// file name.
289
-		// This is to make sure that "createFile" is still triggered
290
-		// (required old code) instead of "updateFile".
291
-		//
292
-		// TODO: resolve chunk file name here and implement "updateFile"
293
-		$path = $this->path . '/' . $name;
294
-		return $this->fileView->file_exists($path);
295
-
296
-	}
297
-
298
-	/**
299
-	 * Deletes all files in this directory, and then itself
300
-	 *
301
-	 * @return void
302
-	 * @throws FileLocked
303
-	 * @throws \Sabre\DAV\Exception\Forbidden
304
-	 */
305
-	public function delete() {
306
-
307
-		if ($this->path === '' || $this->path === '/' || !$this->info->isDeletable()) {
308
-			throw new \Sabre\DAV\Exception\Forbidden();
309
-		}
310
-
311
-		try {
312
-			if (!$this->fileView->rmdir($this->path)) {
313
-				// assume it wasn't possible to remove due to permission issue
314
-				throw new \Sabre\DAV\Exception\Forbidden();
315
-			}
316
-		} catch (ForbiddenException $ex) {
317
-			throw new Forbidden($ex->getMessage(), $ex->getRetry());
318
-		} catch (LockedException $e) {
319
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
320
-		}
321
-	}
322
-
323
-	/**
324
-	 * Returns available diskspace information
325
-	 *
326
-	 * @return array
327
-	 */
328
-	public function getQuotaInfo() {
329
-		if ($this->quotaInfo) {
330
-			return $this->quotaInfo;
331
-		}
332
-		try {
333
-			$storageInfo = \OC_Helper::getStorageInfo($this->info->getPath(), $this->info);
334
-			if ($storageInfo['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED) {
335
-				$free = \OCP\Files\FileInfo::SPACE_UNLIMITED;
336
-			} else {
337
-				$free = $storageInfo['free'];
338
-			}
339
-			$this->quotaInfo = [
340
-				$storageInfo['used'],
341
-				$free
342
-			];
343
-			return $this->quotaInfo;
344
-		} catch (\OCP\Files\StorageNotAvailableException $e) {
345
-			return [0, 0];
346
-		}
347
-	}
348
-
349
-	/**
350
-	 * Moves a node into this collection.
351
-	 *
352
-	 * It is up to the implementors to:
353
-	 *   1. Create the new resource.
354
-	 *   2. Remove the old resource.
355
-	 *   3. Transfer any properties or other data.
356
-	 *
357
-	 * Generally you should make very sure that your collection can easily move
358
-	 * the move.
359
-	 *
360
-	 * If you don't, just return false, which will trigger sabre/dav to handle
361
-	 * the move itself. If you return true from this function, the assumption
362
-	 * is that the move was successful.
363
-	 *
364
-	 * @param string $targetName New local file/collection name.
365
-	 * @param string $fullSourcePath Full path to source node
366
-	 * @param INode $sourceNode Source node itself
367
-	 * @return bool
368
-	 * @throws BadRequest
369
-	 * @throws ServiceUnavailable
370
-	 * @throws Forbidden
371
-	 * @throws FileLocked
372
-	 * @throws \Sabre\DAV\Exception\Forbidden
373
-	 */
374
-	public function moveInto($targetName, $fullSourcePath, INode $sourceNode) {
375
-		if (!$sourceNode instanceof Node) {
376
-			// it's a file of another kind, like FutureFile
377
-			if ($sourceNode instanceof IFile) {
378
-				// fallback to default copy+delete handling
379
-				return false;
380
-			}
381
-			throw new BadRequest('Incompatible node types');
382
-		}
383
-
384
-		if (!$this->fileView) {
385
-			throw new ServiceUnavailable('filesystem not setup');
386
-		}
387
-
388
-		$destinationPath = $this->getPath() . '/' . $targetName;
389
-
390
-
391
-		$targetNodeExists = $this->childExists($targetName);
392
-
393
-		// at getNodeForPath we also check the path for isForbiddenFileOrDir
394
-		// with that we have covered both source and destination
395
-		if ($sourceNode instanceof Directory && $targetNodeExists) {
396
-			throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
397
-		}
398
-
399
-		list($sourceDir,) = \Sabre\Uri\split($sourceNode->getPath());
400
-		$destinationDir = $this->getPath();
401
-
402
-		$sourcePath = $sourceNode->getPath();
403
-
404
-		$isMovableMount = false;
405
-		$sourceMount = \OC::$server->getMountManager()->find($this->fileView->getAbsolutePath($sourcePath));
406
-		$internalPath = $sourceMount->getInternalPath($this->fileView->getAbsolutePath($sourcePath));
407
-		if ($sourceMount instanceof MoveableMount && $internalPath === '') {
408
-			$isMovableMount = true;
409
-		}
410
-
411
-		try {
412
-			$sameFolder = ($sourceDir === $destinationDir);
413
-			// if we're overwriting or same folder
414
-			if ($targetNodeExists || $sameFolder) {
415
-				// note that renaming a share mount point is always allowed
416
-				if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) {
417
-					throw new \Sabre\DAV\Exception\Forbidden();
418
-				}
419
-			} else {
420
-				if (!$this->fileView->isCreatable($destinationDir)) {
421
-					throw new \Sabre\DAV\Exception\Forbidden();
422
-				}
423
-			}
424
-
425
-			if (!$sameFolder) {
426
-				// moving to a different folder, source will be gone, like a deletion
427
-				// note that moving a share mount point is always allowed
428
-				if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) {
429
-					throw new \Sabre\DAV\Exception\Forbidden();
430
-				}
431
-			}
432
-
433
-			$fileName = basename($destinationPath);
434
-			try {
435
-				$this->fileView->verifyPath($destinationDir, $fileName);
436
-			} catch (InvalidPathException $ex) {
437
-				throw new InvalidPath($ex->getMessage());
438
-			}
439
-
440
-			$renameOkay = $this->fileView->rename($sourcePath, $destinationPath);
441
-			if (!$renameOkay) {
442
-				throw new \Sabre\DAV\Exception\Forbidden('');
443
-			}
444
-		} catch (StorageNotAvailableException $e) {
445
-			throw new ServiceUnavailable($e->getMessage());
446
-		} catch (ForbiddenException $ex) {
447
-			throw new Forbidden($ex->getMessage(), $ex->getRetry());
448
-		} catch (LockedException $e) {
449
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
450
-		}
451
-
452
-		return true;
453
-	}
53
+    implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota, \Sabre\DAV\IMoveTarget {
54
+
55
+    /**
56
+     * Cached directory content
57
+     *
58
+     * @var \OCP\Files\FileInfo[]
59
+     */
60
+    private $dirContent;
61
+
62
+    /**
63
+     * Cached quota info
64
+     *
65
+     * @var array
66
+     */
67
+    private $quotaInfo;
68
+
69
+    /**
70
+     * @var ObjectTree|null
71
+     */
72
+    private $tree;
73
+
74
+    /**
75
+     * Sets up the node, expects a full path name
76
+     *
77
+     * @param \OC\Files\View $view
78
+     * @param \OCP\Files\FileInfo $info
79
+     * @param ObjectTree|null $tree
80
+     * @param \OCP\Share\IManager $shareManager
81
+     */
82
+    public function __construct(View $view, FileInfo $info, $tree = null, $shareManager = null) {
83
+        parent::__construct($view, $info, $shareManager);
84
+        $this->tree = $tree;
85
+    }
86
+
87
+    /**
88
+     * Creates a new file in the directory
89
+     *
90
+     * Data will either be supplied as a stream resource, or in certain cases
91
+     * as a string. Keep in mind that you may have to support either.
92
+     *
93
+     * After successful creation of the file, you may choose to return the ETag
94
+     * of the new file here.
95
+     *
96
+     * The returned ETag must be surrounded by double-quotes (The quotes should
97
+     * be part of the actual string).
98
+     *
99
+     * If you cannot accurately determine the ETag, you should not return it.
100
+     * If you don't store the file exactly as-is (you're transforming it
101
+     * somehow) you should also not return an ETag.
102
+     *
103
+     * This means that if a subsequent GET to this new file does not exactly
104
+     * return the same contents of what was submitted here, you are strongly
105
+     * recommended to omit the ETag.
106
+     *
107
+     * @param string $name Name of the file
108
+     * @param resource|string $data Initial payload
109
+     * @return null|string
110
+     * @throws Exception\EntityTooLarge
111
+     * @throws Exception\UnsupportedMediaType
112
+     * @throws FileLocked
113
+     * @throws InvalidPath
114
+     * @throws \Sabre\DAV\Exception
115
+     * @throws \Sabre\DAV\Exception\BadRequest
116
+     * @throws \Sabre\DAV\Exception\Forbidden
117
+     * @throws \Sabre\DAV\Exception\ServiceUnavailable
118
+     */
119
+    public function createFile($name, $data = null) {
120
+
121
+        try {
122
+            // for chunked upload also updating a existing file is a "createFile"
123
+            // because we create all the chunks before re-assemble them to the existing file.
124
+            if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
125
+
126
+                // exit if we can't create a new file and we don't updatable existing file
127
+                $chunkInfo = \OC_FileChunking::decodeName($name);
128
+                if (!$this->fileView->isCreatable($this->path) &&
129
+                    !$this->fileView->isUpdatable($this->path . '/' . $chunkInfo['name'])
130
+                ) {
131
+                    throw new \Sabre\DAV\Exception\Forbidden();
132
+                }
133
+
134
+            } else {
135
+                // For non-chunked upload it is enough to check if we can create a new file
136
+                if (!$this->fileView->isCreatable($this->path)) {
137
+                    throw new \Sabre\DAV\Exception\Forbidden();
138
+                }
139
+            }
140
+
141
+            $this->fileView->verifyPath($this->path, $name);
142
+
143
+            $path = $this->fileView->getAbsolutePath($this->path) . '/' . $name;
144
+            // in case the file already exists/overwriting
145
+            $info = $this->fileView->getFileInfo($this->path . '/' . $name);
146
+            if (!$info) {
147
+                // use a dummy FileInfo which is acceptable here since it will be refreshed after the put is complete
148
+                $info = new \OC\Files\FileInfo($path, null, null, [], null);
149
+            }
150
+            $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info);
151
+
152
+            // only allow 1 process to upload a file at once but still allow reading the file while writing the part file
153
+            $node->acquireLock(ILockingProvider::LOCK_SHARED);
154
+            $this->fileView->lockFile($path . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
155
+
156
+            $result = $node->put($data);
157
+
158
+            $this->fileView->unlockFile($path . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
159
+            $node->releaseLock(ILockingProvider::LOCK_SHARED);
160
+            return $result;
161
+        } catch (\OCP\Files\StorageNotAvailableException $e) {
162
+            throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), $e->getCode(), $e);
163
+        } catch (InvalidPathException $ex) {
164
+            throw new InvalidPath($ex->getMessage(), false, $ex);
165
+        } catch (ForbiddenException $ex) {
166
+            throw new Forbidden($ex->getMessage(), $ex->getRetry(), $ex);
167
+        } catch (LockedException $e) {
168
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
169
+        }
170
+    }
171
+
172
+    /**
173
+     * Creates a new subdirectory
174
+     *
175
+     * @param string $name
176
+     * @throws FileLocked
177
+     * @throws InvalidPath
178
+     * @throws \Sabre\DAV\Exception\Forbidden
179
+     * @throws \Sabre\DAV\Exception\ServiceUnavailable
180
+     */
181
+    public function createDirectory($name) {
182
+        try {
183
+            if (!$this->info->isCreatable()) {
184
+                throw new \Sabre\DAV\Exception\Forbidden();
185
+            }
186
+
187
+            $this->fileView->verifyPath($this->path, $name);
188
+            $newPath = $this->path . '/' . $name;
189
+            if (!$this->fileView->mkdir($newPath)) {
190
+                throw new \Sabre\DAV\Exception\Forbidden('Could not create directory ' . $newPath);
191
+            }
192
+        } catch (\OCP\Files\StorageNotAvailableException $e) {
193
+            throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
194
+        } catch (InvalidPathException $ex) {
195
+            throw new InvalidPath($ex->getMessage());
196
+        } catch (ForbiddenException $ex) {
197
+            throw new Forbidden($ex->getMessage(), $ex->getRetry());
198
+        } catch (LockedException $e) {
199
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
200
+        }
201
+    }
202
+
203
+    /**
204
+     * Returns a specific child node, referenced by its name
205
+     *
206
+     * @param string $name
207
+     * @param \OCP\Files\FileInfo $info
208
+     * @return \Sabre\DAV\INode
209
+     * @throws InvalidPath
210
+     * @throws \Sabre\DAV\Exception\NotFound
211
+     * @throws \Sabre\DAV\Exception\ServiceUnavailable
212
+     */
213
+    public function getChild($name, $info = null) {
214
+        if (!$this->info->isReadable()) {
215
+            // avoid detecting files through this way
216
+            throw new NotFound();
217
+        }
218
+
219
+        $path = $this->path . '/' . $name;
220
+        if (is_null($info)) {
221
+            try {
222
+                $this->fileView->verifyPath($this->path, $name);
223
+                $info = $this->fileView->getFileInfo($path);
224
+            } catch (\OCP\Files\StorageNotAvailableException $e) {
225
+                throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
226
+            } catch (InvalidPathException $ex) {
227
+                throw new InvalidPath($ex->getMessage());
228
+            } catch (ForbiddenException $e) {
229
+                throw new \Sabre\DAV\Exception\Forbidden();
230
+            }
231
+        }
232
+
233
+        if (!$info) {
234
+            throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located');
235
+        }
236
+
237
+        if ($info['mimetype'] === 'httpd/unix-directory') {
238
+            $node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this->tree, $this->shareManager);
239
+        } else {
240
+            $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info, $this->shareManager);
241
+        }
242
+        if ($this->tree) {
243
+            $this->tree->cacheNode($node);
244
+        }
245
+        return $node;
246
+    }
247
+
248
+    /**
249
+     * Returns an array with all the child nodes
250
+     *
251
+     * @return \Sabre\DAV\INode[]
252
+     * @throws \Sabre\DAV\Exception\Locked
253
+     * @throws \OCA\DAV\Connector\Sabre\Exception\Forbidden
254
+     */
255
+    public function getChildren() {
256
+        if (!is_null($this->dirContent)) {
257
+            return $this->dirContent;
258
+        }
259
+        try {
260
+            if (!$this->info->isReadable()) {
261
+                // return 403 instead of 404 because a 404 would make
262
+                // the caller believe that the collection itself does not exist
263
+                throw new Forbidden('No read permissions');
264
+            }
265
+            $folderContent = $this->fileView->getDirectoryContent($this->path);
266
+        } catch (LockedException $e) {
267
+            throw new Locked();
268
+        }
269
+
270
+        $nodes = [];
271
+        foreach ($folderContent as $info) {
272
+            $node = $this->getChild($info->getName(), $info);
273
+            $nodes[] = $node;
274
+        }
275
+        $this->dirContent = $nodes;
276
+        return $this->dirContent;
277
+    }
278
+
279
+    /**
280
+     * Checks if a child exists.
281
+     *
282
+     * @param string $name
283
+     * @return bool
284
+     */
285
+    public function childExists($name) {
286
+        // note: here we do NOT resolve the chunk file name to the real file name
287
+        // to make sure we return false when checking for file existence with a chunk
288
+        // file name.
289
+        // This is to make sure that "createFile" is still triggered
290
+        // (required old code) instead of "updateFile".
291
+        //
292
+        // TODO: resolve chunk file name here and implement "updateFile"
293
+        $path = $this->path . '/' . $name;
294
+        return $this->fileView->file_exists($path);
295
+
296
+    }
297
+
298
+    /**
299
+     * Deletes all files in this directory, and then itself
300
+     *
301
+     * @return void
302
+     * @throws FileLocked
303
+     * @throws \Sabre\DAV\Exception\Forbidden
304
+     */
305
+    public function delete() {
306
+
307
+        if ($this->path === '' || $this->path === '/' || !$this->info->isDeletable()) {
308
+            throw new \Sabre\DAV\Exception\Forbidden();
309
+        }
310
+
311
+        try {
312
+            if (!$this->fileView->rmdir($this->path)) {
313
+                // assume it wasn't possible to remove due to permission issue
314
+                throw new \Sabre\DAV\Exception\Forbidden();
315
+            }
316
+        } catch (ForbiddenException $ex) {
317
+            throw new Forbidden($ex->getMessage(), $ex->getRetry());
318
+        } catch (LockedException $e) {
319
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
320
+        }
321
+    }
322
+
323
+    /**
324
+     * Returns available diskspace information
325
+     *
326
+     * @return array
327
+     */
328
+    public function getQuotaInfo() {
329
+        if ($this->quotaInfo) {
330
+            return $this->quotaInfo;
331
+        }
332
+        try {
333
+            $storageInfo = \OC_Helper::getStorageInfo($this->info->getPath(), $this->info);
334
+            if ($storageInfo['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED) {
335
+                $free = \OCP\Files\FileInfo::SPACE_UNLIMITED;
336
+            } else {
337
+                $free = $storageInfo['free'];
338
+            }
339
+            $this->quotaInfo = [
340
+                $storageInfo['used'],
341
+                $free
342
+            ];
343
+            return $this->quotaInfo;
344
+        } catch (\OCP\Files\StorageNotAvailableException $e) {
345
+            return [0, 0];
346
+        }
347
+    }
348
+
349
+    /**
350
+     * Moves a node into this collection.
351
+     *
352
+     * It is up to the implementors to:
353
+     *   1. Create the new resource.
354
+     *   2. Remove the old resource.
355
+     *   3. Transfer any properties or other data.
356
+     *
357
+     * Generally you should make very sure that your collection can easily move
358
+     * the move.
359
+     *
360
+     * If you don't, just return false, which will trigger sabre/dav to handle
361
+     * the move itself. If you return true from this function, the assumption
362
+     * is that the move was successful.
363
+     *
364
+     * @param string $targetName New local file/collection name.
365
+     * @param string $fullSourcePath Full path to source node
366
+     * @param INode $sourceNode Source node itself
367
+     * @return bool
368
+     * @throws BadRequest
369
+     * @throws ServiceUnavailable
370
+     * @throws Forbidden
371
+     * @throws FileLocked
372
+     * @throws \Sabre\DAV\Exception\Forbidden
373
+     */
374
+    public function moveInto($targetName, $fullSourcePath, INode $sourceNode) {
375
+        if (!$sourceNode instanceof Node) {
376
+            // it's a file of another kind, like FutureFile
377
+            if ($sourceNode instanceof IFile) {
378
+                // fallback to default copy+delete handling
379
+                return false;
380
+            }
381
+            throw new BadRequest('Incompatible node types');
382
+        }
383
+
384
+        if (!$this->fileView) {
385
+            throw new ServiceUnavailable('filesystem not setup');
386
+        }
387
+
388
+        $destinationPath = $this->getPath() . '/' . $targetName;
389
+
390
+
391
+        $targetNodeExists = $this->childExists($targetName);
392
+
393
+        // at getNodeForPath we also check the path for isForbiddenFileOrDir
394
+        // with that we have covered both source and destination
395
+        if ($sourceNode instanceof Directory && $targetNodeExists) {
396
+            throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
397
+        }
398
+
399
+        list($sourceDir,) = \Sabre\Uri\split($sourceNode->getPath());
400
+        $destinationDir = $this->getPath();
401
+
402
+        $sourcePath = $sourceNode->getPath();
403
+
404
+        $isMovableMount = false;
405
+        $sourceMount = \OC::$server->getMountManager()->find($this->fileView->getAbsolutePath($sourcePath));
406
+        $internalPath = $sourceMount->getInternalPath($this->fileView->getAbsolutePath($sourcePath));
407
+        if ($sourceMount instanceof MoveableMount && $internalPath === '') {
408
+            $isMovableMount = true;
409
+        }
410
+
411
+        try {
412
+            $sameFolder = ($sourceDir === $destinationDir);
413
+            // if we're overwriting or same folder
414
+            if ($targetNodeExists || $sameFolder) {
415
+                // note that renaming a share mount point is always allowed
416
+                if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) {
417
+                    throw new \Sabre\DAV\Exception\Forbidden();
418
+                }
419
+            } else {
420
+                if (!$this->fileView->isCreatable($destinationDir)) {
421
+                    throw new \Sabre\DAV\Exception\Forbidden();
422
+                }
423
+            }
424
+
425
+            if (!$sameFolder) {
426
+                // moving to a different folder, source will be gone, like a deletion
427
+                // note that moving a share mount point is always allowed
428
+                if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) {
429
+                    throw new \Sabre\DAV\Exception\Forbidden();
430
+                }
431
+            }
432
+
433
+            $fileName = basename($destinationPath);
434
+            try {
435
+                $this->fileView->verifyPath($destinationDir, $fileName);
436
+            } catch (InvalidPathException $ex) {
437
+                throw new InvalidPath($ex->getMessage());
438
+            }
439
+
440
+            $renameOkay = $this->fileView->rename($sourcePath, $destinationPath);
441
+            if (!$renameOkay) {
442
+                throw new \Sabre\DAV\Exception\Forbidden('');
443
+            }
444
+        } catch (StorageNotAvailableException $e) {
445
+            throw new ServiceUnavailable($e->getMessage());
446
+        } catch (ForbiddenException $ex) {
447
+            throw new Forbidden($ex->getMessage(), $ex->getRetry());
448
+        } catch (LockedException $e) {
449
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
450
+        }
451
+
452
+        return true;
453
+    }
454 454
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/File.php 1 patch
Indentation   +601 added lines, -601 removed lines patch added patch discarded remove patch
@@ -70,605 +70,605 @@
 block discarded – undo
70 70
 
71 71
 class File extends Node implements IFile {
72 72
 
73
-	protected $request;
74
-
75
-	/**
76
-	 * Sets up the node, expects a full path name
77
-	 *
78
-	 * @param \OC\Files\View $view
79
-	 * @param \OCP\Files\FileInfo $info
80
-	 * @param \OCP\Share\IManager $shareManager
81
-	 * @param \OC\AppFramework\Http\Request $request
82
-	 */
83
-	public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
84
-		parent::__construct($view, $info, $shareManager);
85
-
86
-		if (isset($request)) {
87
-			$this->request = $request;
88
-		} else {
89
-			$this->request = \OC::$server->getRequest();
90
-		}
91
-	}
92
-
93
-	/**
94
-	 * Updates the data
95
-	 *
96
-	 * The data argument is a readable stream resource.
97
-	 *
98
-	 * After a successful put operation, you may choose to return an ETag. The
99
-	 * etag must always be surrounded by double-quotes. These quotes must
100
-	 * appear in the actual string you're returning.
101
-	 *
102
-	 * Clients may use the ETag from a PUT request to later on make sure that
103
-	 * when they update the file, the contents haven't changed in the mean
104
-	 * time.
105
-	 *
106
-	 * If you don't plan to store the file byte-by-byte, and you return a
107
-	 * different object on a subsequent GET you are strongly recommended to not
108
-	 * return an ETag, and just return null.
109
-	 *
110
-	 * @param resource $data
111
-	 *
112
-	 * @throws Forbidden
113
-	 * @throws UnsupportedMediaType
114
-	 * @throws BadRequest
115
-	 * @throws Exception
116
-	 * @throws EntityTooLarge
117
-	 * @throws ServiceUnavailable
118
-	 * @throws FileLocked
119
-	 * @return string|null
120
-	 */
121
-	public function put($data) {
122
-		try {
123
-			$exists = $this->fileView->file_exists($this->path);
124
-			if ($this->info && $exists && !$this->info->isUpdateable()) {
125
-				throw new Forbidden();
126
-			}
127
-		} catch (StorageNotAvailableException $e) {
128
-			throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
129
-		}
130
-
131
-		// verify path of the target
132
-		$this->verifyPath();
133
-
134
-		// chunked handling
135
-		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
136
-			try {
137
-				return $this->createFileChunked($data);
138
-			} catch (\Exception $e) {
139
-				$this->convertToSabreException($e);
140
-			}
141
-		}
142
-
143
-		/** @var Storage $partStorage */
144
-		list($partStorage) = $this->fileView->resolvePath($this->path);
145
-		$needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);
146
-
147
-		$view = \OC\Files\Filesystem::getView();
148
-
149
-		if ($needsPartFile) {
150
-			// mark file as partial while uploading (ignored by the scanner)
151
-			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
152
-
153
-			if (!$view->isCreatable($partFilePath) && $view->isUpdatable($this->path)) {
154
-				$needsPartFile = false;
155
-			}
156
-		}
157
-		if (!$needsPartFile) {
158
-			// upload file directly as the final path
159
-			$partFilePath = $this->path;
160
-
161
-			if ($view && !$this->emitPreHooks($exists)) {
162
-				throw new Exception('Could not write to final file, canceled by hook');
163
-			}
164
-		}
165
-
166
-		// the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
167
-		/** @var \OC\Files\Storage\Storage $partStorage */
168
-		list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
169
-		/** @var \OC\Files\Storage\Storage $storage */
170
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
171
-		try {
172
-			if (!$needsPartFile) {
173
-				$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
174
-			}
175
-
176
-			if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
177
-
178
-				if (!is_resource($data)) {
179
-					$tmpData = fopen('php://temp', 'r+');
180
-					if ($data !== null) {
181
-						fwrite($tmpData, $data);
182
-						rewind($tmpData);
183
-					}
184
-					$data = $tmpData;
185
-				}
186
-
187
-				$isEOF = false;
188
-				$wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF) {
189
-					$isEOF = feof($stream);
190
-				});
191
-
192
-				$count = $partStorage->writeStream($internalPartPath, $wrappedData);
193
-				$result = $count > 0;
194
-
195
-				if ($result === false) {
196
-					$result = $isEOF;
197
-					if (is_resource($wrappedData)) {
198
-						$result = feof($wrappedData);
199
-					}
200
-				}
201
-
202
-			} else {
203
-				$target = $partStorage->fopen($internalPartPath, 'wb');
204
-				if ($target === false) {
205
-					\OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
206
-					// because we have no clue about the cause we can only throw back a 500/Internal Server Error
207
-					throw new Exception('Could not write file contents');
208
-				}
209
-				list($count, $result) = \OC_Helper::streamCopy($data, $target);
210
-				fclose($target);
211
-			}
212
-
213
-			if ($result === false) {
214
-				$expected = -1;
215
-				if (isset($_SERVER['CONTENT_LENGTH'])) {
216
-					$expected = $_SERVER['CONTENT_LENGTH'];
217
-				}
218
-				if ($expected !== "0") {
219
-					throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
220
-				}
221
-			}
222
-
223
-			// if content length is sent by client:
224
-			// double check if the file was fully received
225
-			// compare expected and actual size
226
-			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
227
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
228
-				if ($count !== $expected) {
229
-					throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $count . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
230
-				}
231
-			}
232
-
233
-		} catch (\Exception $e) {
234
-			$context = [];
235
-
236
-			if ($e instanceof LockedException) {
237
-				$context['level'] = ILogger::DEBUG;
238
-			}
239
-
240
-			\OC::$server->getLogger()->logException($e, $context);
241
-			if ($needsPartFile) {
242
-				$partStorage->unlink($internalPartPath);
243
-			}
244
-			$this->convertToSabreException($e);
245
-		}
246
-
247
-		try {
248
-			if ($needsPartFile) {
249
-				if ($view && !$this->emitPreHooks($exists)) {
250
-					$partStorage->unlink($internalPartPath);
251
-					throw new Exception('Could not rename part file to final file, canceled by hook');
252
-				}
253
-				try {
254
-					$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
255
-				} catch (LockedException $e) {
256
-					// during very large uploads, the shared lock we got at the start might have been expired
257
-					// meaning that the above lock can fail not just only because somebody else got a shared lock
258
-					// or because there is no existing shared lock to make exclusive
259
-					//
260
-					// Thus we try to get a new exclusive lock, if the original lock failed because of a different shared
261
-					// lock this will still fail, if our original shared lock expired the new lock will be successful and
262
-					// the entire operation will be safe
263
-
264
-					try {
265
-						$this->acquireLock(ILockingProvider::LOCK_EXCLUSIVE);
266
-					} catch (LockedException $ex) {
267
-						if ($needsPartFile) {
268
-							$partStorage->unlink($internalPartPath);
269
-						}
270
-						throw new FileLocked($e->getMessage(), $e->getCode(), $e);
271
-					}
272
-				}
273
-
274
-				// rename to correct path
275
-				try {
276
-					$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
277
-					$fileExists = $storage->file_exists($internalPath);
278
-					if ($renameOkay === false || $fileExists === false) {
279
-						\OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
280
-						throw new Exception('Could not rename part file to final file');
281
-					}
282
-				} catch (ForbiddenException $ex) {
283
-					throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
284
-				} catch (\Exception $e) {
285
-					$partStorage->unlink($internalPartPath);
286
-					$this->convertToSabreException($e);
287
-				}
288
-			}
289
-
290
-			// since we skipped the view we need to scan and emit the hooks ourselves
291
-			$storage->getUpdater()->update($internalPath);
292
-
293
-			try {
294
-				$this->changeLock(ILockingProvider::LOCK_SHARED);
295
-			} catch (LockedException $e) {
296
-				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
297
-			}
298
-
299
-			// allow sync clients to send the mtime along in a header
300
-			if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
301
-				$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
302
-				if ($this->fileView->touch($this->path, $mtime)) {
303
-					$this->header('X-OC-MTime: accepted');
304
-				}
305
-			}
306
-
307
-			$fileInfoUpdate = [
308
-				'upload_time' => time()
309
-			];
310
-
311
-			// allow sync clients to send the creation time along in a header
312
-			if (isset($this->request->server['HTTP_X_OC_CTIME'])) {
313
-				$ctime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_CTIME']);
314
-				$fileInfoUpdate['creation_time'] = $ctime;
315
-				$this->header('X-OC-CTime: accepted');
316
-			}
317
-
318
-			$this->fileView->putFileInfo($this->path, $fileInfoUpdate);
319
-
320
-			if ($view) {
321
-				$this->emitPostHooks($exists);
322
-			}
323
-
324
-			$this->refreshInfo();
325
-
326
-			if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
327
-				$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
328
-				$this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
329
-				$this->refreshInfo();
330
-			} else if ($this->getChecksum() !== null && $this->getChecksum() !== '') {
331
-				$this->fileView->putFileInfo($this->path, ['checksum' => '']);
332
-				$this->refreshInfo();
333
-			}
334
-
335
-		} catch (StorageNotAvailableException $e) {
336
-			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
337
-		}
338
-
339
-		return '"' . $this->info->getEtag() . '"';
340
-	}
341
-
342
-	private function getPartFileBasePath($path) {
343
-		$partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
344
-		if ($partFileInStorage) {
345
-			return $path;
346
-		} else {
347
-			return md5($path); // will place it in the root of the view with a unique name
348
-		}
349
-	}
350
-
351
-	/**
352
-	 * @param string $path
353
-	 */
354
-	private function emitPreHooks($exists, $path = null) {
355
-		if (is_null($path)) {
356
-			$path = $this->path;
357
-		}
358
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
359
-		$run = true;
360
-
361
-		if (!$exists) {
362
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, [
363
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
364
-				\OC\Files\Filesystem::signal_param_run => &$run,
365
-			]);
366
-		} else {
367
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, [
368
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
369
-				\OC\Files\Filesystem::signal_param_run => &$run,
370
-			]);
371
-		}
372
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, [
373
-			\OC\Files\Filesystem::signal_param_path => $hookPath,
374
-			\OC\Files\Filesystem::signal_param_run => &$run,
375
-		]);
376
-		return $run;
377
-	}
378
-
379
-	/**
380
-	 * @param string $path
381
-	 */
382
-	private function emitPostHooks($exists, $path = null) {
383
-		if (is_null($path)) {
384
-			$path = $this->path;
385
-		}
386
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
387
-		if (!$exists) {
388
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [
389
-				\OC\Files\Filesystem::signal_param_path => $hookPath
390
-			]);
391
-		} else {
392
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, [
393
-				\OC\Files\Filesystem::signal_param_path => $hookPath
394
-			]);
395
-		}
396
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, [
397
-			\OC\Files\Filesystem::signal_param_path => $hookPath
398
-		]);
399
-	}
400
-
401
-	/**
402
-	 * Returns the data
403
-	 *
404
-	 * @return resource
405
-	 * @throws Forbidden
406
-	 * @throws ServiceUnavailable
407
-	 */
408
-	public function get() {
409
-		//throw exception if encryption is disabled but files are still encrypted
410
-		try {
411
-			if (!$this->info->isReadable()) {
412
-				// do a if the file did not exist
413
-				throw new NotFound();
414
-			}
415
-			try {
416
-				$res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
417
-			} catch (\Exception $e) {
418
-				$this->convertToSabreException($e);
419
-			}
420
-			if ($res === false) {
421
-				throw new ServiceUnavailable("Could not open file");
422
-			}
423
-			return $res;
424
-		} catch (GenericEncryptionException $e) {
425
-			// returning 503 will allow retry of the operation at a later point in time
426
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
427
-		} catch (StorageNotAvailableException $e) {
428
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
429
-		} catch (ForbiddenException $ex) {
430
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
431
-		} catch (LockedException $e) {
432
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
433
-		}
434
-	}
435
-
436
-	/**
437
-	 * Delete the current file
438
-	 *
439
-	 * @throws Forbidden
440
-	 * @throws ServiceUnavailable
441
-	 */
442
-	public function delete() {
443
-		if (!$this->info->isDeletable()) {
444
-			throw new Forbidden();
445
-		}
446
-
447
-		try {
448
-			if (!$this->fileView->unlink($this->path)) {
449
-				// assume it wasn't possible to delete due to permissions
450
-				throw new Forbidden();
451
-			}
452
-		} catch (StorageNotAvailableException $e) {
453
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
454
-		} catch (ForbiddenException $ex) {
455
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
456
-		} catch (LockedException $e) {
457
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
458
-		}
459
-	}
460
-
461
-	/**
462
-	 * Returns the mime-type for a file
463
-	 *
464
-	 * If null is returned, we'll assume application/octet-stream
465
-	 *
466
-	 * @return string
467
-	 */
468
-	public function getContentType() {
469
-		$mimeType = $this->info->getMimetype();
470
-
471
-		// PROPFIND needs to return the correct mime type, for consistency with the web UI
472
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
473
-			return $mimeType;
474
-		}
475
-		return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
476
-	}
477
-
478
-	/**
479
-	 * @return array|false
480
-	 */
481
-	public function getDirectDownload() {
482
-		if (\OCP\App::isEnabled('encryption')) {
483
-			return [];
484
-		}
485
-		/** @var \OCP\Files\Storage $storage */
486
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
487
-		if (is_null($storage)) {
488
-			return [];
489
-		}
490
-
491
-		return $storage->getDirectDownload($internalPath);
492
-	}
493
-
494
-	/**
495
-	 * @param resource $data
496
-	 * @return null|string
497
-	 * @throws Exception
498
-	 * @throws BadRequest
499
-	 * @throws NotImplemented
500
-	 * @throws ServiceUnavailable
501
-	 */
502
-	private function createFileChunked($data) {
503
-		list($path, $name) = \Sabre\Uri\split($this->path);
504
-
505
-		$info = \OC_FileChunking::decodeName($name);
506
-		if (empty($info)) {
507
-			throw new NotImplemented('Invalid chunk name');
508
-		}
509
-
510
-		$chunk_handler = new \OC_FileChunking($info);
511
-		$bytesWritten = $chunk_handler->store($info['index'], $data);
512
-
513
-		//detect aborted upload
514
-		if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
515
-			if (isset($_SERVER['CONTENT_LENGTH'])) {
516
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
517
-				if ($bytesWritten !== $expected) {
518
-					$chunk_handler->remove($info['index']);
519
-					throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $bytesWritten . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
520
-				}
521
-			}
522
-		}
523
-
524
-		if ($chunk_handler->isComplete()) {
525
-			/** @var Storage $storage */
526
-			list($storage,) = $this->fileView->resolvePath($path);
527
-			$needsPartFile = $storage->needsPartFile();
528
-			$partFile = null;
529
-
530
-			$targetPath = $path . '/' . $info['name'];
531
-			/** @var \OC\Files\Storage\Storage $targetStorage */
532
-			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
533
-
534
-			$exists = $this->fileView->file_exists($targetPath);
535
-
536
-			try {
537
-				$this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
538
-
539
-				$this->emitPreHooks($exists, $targetPath);
540
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
541
-				/** @var \OC\Files\Storage\Storage $targetStorage */
542
-				list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
543
-
544
-				if ($needsPartFile) {
545
-					// we first assembly the target file as a part file
546
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
547
-					/** @var \OC\Files\Storage\Storage $targetStorage */
548
-					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
549
-
550
-
551
-					$chunk_handler->file_assemble($partStorage, $partInternalPath);
552
-
553
-					// here is the final atomic rename
554
-					$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
555
-					$fileExists = $targetStorage->file_exists($targetInternalPath);
556
-					if ($renameOkay === false || $fileExists === false) {
557
-						\OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
558
-						// only delete if an error occurred and the target file was already created
559
-						if ($fileExists) {
560
-							// set to null to avoid double-deletion when handling exception
561
-							// stray part file
562
-							$partFile = null;
563
-							$targetStorage->unlink($targetInternalPath);
564
-						}
565
-						$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
566
-						throw new Exception('Could not rename part file assembled from chunks');
567
-					}
568
-				} else {
569
-					// assemble directly into the final file
570
-					$chunk_handler->file_assemble($targetStorage, $targetInternalPath);
571
-				}
572
-
573
-				// allow sync clients to send the mtime along in a header
574
-				if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
575
-					$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
576
-					if ($targetStorage->touch($targetInternalPath, $mtime)) {
577
-						$this->header('X-OC-MTime: accepted');
578
-					}
579
-				}
580
-
581
-				// since we skipped the view we need to scan and emit the hooks ourselves
582
-				$targetStorage->getUpdater()->update($targetInternalPath);
583
-
584
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
585
-
586
-				$this->emitPostHooks($exists, $targetPath);
587
-
588
-				// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
589
-				$info = $this->fileView->getFileInfo($targetPath);
590
-
591
-				if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
592
-					$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
593
-					$this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
594
-				} else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
595
-					$this->fileView->putFileInfo($this->path, ['checksum' => '']);
596
-				}
597
-
598
-				$this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
599
-
600
-				return $info->getEtag();
601
-			} catch (\Exception $e) {
602
-				if ($partFile !== null) {
603
-					$targetStorage->unlink($targetInternalPath);
604
-				}
605
-				$this->convertToSabreException($e);
606
-			}
607
-		}
608
-
609
-		return null;
610
-	}
611
-
612
-	/**
613
-	 * Convert the given exception to a SabreException instance
614
-	 *
615
-	 * @param \Exception $e
616
-	 *
617
-	 * @throws \Sabre\DAV\Exception
618
-	 */
619
-	private function convertToSabreException(\Exception $e) {
620
-		if ($e instanceof \Sabre\DAV\Exception) {
621
-			throw $e;
622
-		}
623
-		if ($e instanceof NotPermittedException) {
624
-			// a more general case - due to whatever reason the content could not be written
625
-			throw new Forbidden($e->getMessage(), 0, $e);
626
-		}
627
-		if ($e instanceof ForbiddenException) {
628
-			// the path for the file was forbidden
629
-			throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
630
-		}
631
-		if ($e instanceof EntityTooLargeException) {
632
-			// the file is too big to be stored
633
-			throw new EntityTooLarge($e->getMessage(), 0, $e);
634
-		}
635
-		if ($e instanceof InvalidContentException) {
636
-			// the file content is not permitted
637
-			throw new UnsupportedMediaType($e->getMessage(), 0, $e);
638
-		}
639
-		if ($e instanceof InvalidPathException) {
640
-			// the path for the file was not valid
641
-			// TODO: find proper http status code for this case
642
-			throw new Forbidden($e->getMessage(), 0, $e);
643
-		}
644
-		if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
645
-			// the file is currently being written to by another process
646
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
647
-		}
648
-		if ($e instanceof GenericEncryptionException) {
649
-			// returning 503 will allow retry of the operation at a later point in time
650
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
651
-		}
652
-		if ($e instanceof StorageNotAvailableException) {
653
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
654
-		}
655
-		if ($e instanceof NotFoundException) {
656
-			throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
657
-		}
658
-
659
-		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
660
-	}
661
-
662
-	/**
663
-	 * Get the checksum for this file
664
-	 *
665
-	 * @return string
666
-	 */
667
-	public function getChecksum() {
668
-		return $this->info->getChecksum();
669
-	}
670
-
671
-	protected function header($string) {
672
-		\header($string);
673
-	}
73
+    protected $request;
74
+
75
+    /**
76
+     * Sets up the node, expects a full path name
77
+     *
78
+     * @param \OC\Files\View $view
79
+     * @param \OCP\Files\FileInfo $info
80
+     * @param \OCP\Share\IManager $shareManager
81
+     * @param \OC\AppFramework\Http\Request $request
82
+     */
83
+    public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
84
+        parent::__construct($view, $info, $shareManager);
85
+
86
+        if (isset($request)) {
87
+            $this->request = $request;
88
+        } else {
89
+            $this->request = \OC::$server->getRequest();
90
+        }
91
+    }
92
+
93
+    /**
94
+     * Updates the data
95
+     *
96
+     * The data argument is a readable stream resource.
97
+     *
98
+     * After a successful put operation, you may choose to return an ETag. The
99
+     * etag must always be surrounded by double-quotes. These quotes must
100
+     * appear in the actual string you're returning.
101
+     *
102
+     * Clients may use the ETag from a PUT request to later on make sure that
103
+     * when they update the file, the contents haven't changed in the mean
104
+     * time.
105
+     *
106
+     * If you don't plan to store the file byte-by-byte, and you return a
107
+     * different object on a subsequent GET you are strongly recommended to not
108
+     * return an ETag, and just return null.
109
+     *
110
+     * @param resource $data
111
+     *
112
+     * @throws Forbidden
113
+     * @throws UnsupportedMediaType
114
+     * @throws BadRequest
115
+     * @throws Exception
116
+     * @throws EntityTooLarge
117
+     * @throws ServiceUnavailable
118
+     * @throws FileLocked
119
+     * @return string|null
120
+     */
121
+    public function put($data) {
122
+        try {
123
+            $exists = $this->fileView->file_exists($this->path);
124
+            if ($this->info && $exists && !$this->info->isUpdateable()) {
125
+                throw new Forbidden();
126
+            }
127
+        } catch (StorageNotAvailableException $e) {
128
+            throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
129
+        }
130
+
131
+        // verify path of the target
132
+        $this->verifyPath();
133
+
134
+        // chunked handling
135
+        if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
136
+            try {
137
+                return $this->createFileChunked($data);
138
+            } catch (\Exception $e) {
139
+                $this->convertToSabreException($e);
140
+            }
141
+        }
142
+
143
+        /** @var Storage $partStorage */
144
+        list($partStorage) = $this->fileView->resolvePath($this->path);
145
+        $needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);
146
+
147
+        $view = \OC\Files\Filesystem::getView();
148
+
149
+        if ($needsPartFile) {
150
+            // mark file as partial while uploading (ignored by the scanner)
151
+            $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
152
+
153
+            if (!$view->isCreatable($partFilePath) && $view->isUpdatable($this->path)) {
154
+                $needsPartFile = false;
155
+            }
156
+        }
157
+        if (!$needsPartFile) {
158
+            // upload file directly as the final path
159
+            $partFilePath = $this->path;
160
+
161
+            if ($view && !$this->emitPreHooks($exists)) {
162
+                throw new Exception('Could not write to final file, canceled by hook');
163
+            }
164
+        }
165
+
166
+        // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
167
+        /** @var \OC\Files\Storage\Storage $partStorage */
168
+        list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
169
+        /** @var \OC\Files\Storage\Storage $storage */
170
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
171
+        try {
172
+            if (!$needsPartFile) {
173
+                $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
174
+            }
175
+
176
+            if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
177
+
178
+                if (!is_resource($data)) {
179
+                    $tmpData = fopen('php://temp', 'r+');
180
+                    if ($data !== null) {
181
+                        fwrite($tmpData, $data);
182
+                        rewind($tmpData);
183
+                    }
184
+                    $data = $tmpData;
185
+                }
186
+
187
+                $isEOF = false;
188
+                $wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF) {
189
+                    $isEOF = feof($stream);
190
+                });
191
+
192
+                $count = $partStorage->writeStream($internalPartPath, $wrappedData);
193
+                $result = $count > 0;
194
+
195
+                if ($result === false) {
196
+                    $result = $isEOF;
197
+                    if (is_resource($wrappedData)) {
198
+                        $result = feof($wrappedData);
199
+                    }
200
+                }
201
+
202
+            } else {
203
+                $target = $partStorage->fopen($internalPartPath, 'wb');
204
+                if ($target === false) {
205
+                    \OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
206
+                    // because we have no clue about the cause we can only throw back a 500/Internal Server Error
207
+                    throw new Exception('Could not write file contents');
208
+                }
209
+                list($count, $result) = \OC_Helper::streamCopy($data, $target);
210
+                fclose($target);
211
+            }
212
+
213
+            if ($result === false) {
214
+                $expected = -1;
215
+                if (isset($_SERVER['CONTENT_LENGTH'])) {
216
+                    $expected = $_SERVER['CONTENT_LENGTH'];
217
+                }
218
+                if ($expected !== "0") {
219
+                    throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
220
+                }
221
+            }
222
+
223
+            // if content length is sent by client:
224
+            // double check if the file was fully received
225
+            // compare expected and actual size
226
+            if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
227
+                $expected = (int)$_SERVER['CONTENT_LENGTH'];
228
+                if ($count !== $expected) {
229
+                    throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $count . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
230
+                }
231
+            }
232
+
233
+        } catch (\Exception $e) {
234
+            $context = [];
235
+
236
+            if ($e instanceof LockedException) {
237
+                $context['level'] = ILogger::DEBUG;
238
+            }
239
+
240
+            \OC::$server->getLogger()->logException($e, $context);
241
+            if ($needsPartFile) {
242
+                $partStorage->unlink($internalPartPath);
243
+            }
244
+            $this->convertToSabreException($e);
245
+        }
246
+
247
+        try {
248
+            if ($needsPartFile) {
249
+                if ($view && !$this->emitPreHooks($exists)) {
250
+                    $partStorage->unlink($internalPartPath);
251
+                    throw new Exception('Could not rename part file to final file, canceled by hook');
252
+                }
253
+                try {
254
+                    $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
255
+                } catch (LockedException $e) {
256
+                    // during very large uploads, the shared lock we got at the start might have been expired
257
+                    // meaning that the above lock can fail not just only because somebody else got a shared lock
258
+                    // or because there is no existing shared lock to make exclusive
259
+                    //
260
+                    // Thus we try to get a new exclusive lock, if the original lock failed because of a different shared
261
+                    // lock this will still fail, if our original shared lock expired the new lock will be successful and
262
+                    // the entire operation will be safe
263
+
264
+                    try {
265
+                        $this->acquireLock(ILockingProvider::LOCK_EXCLUSIVE);
266
+                    } catch (LockedException $ex) {
267
+                        if ($needsPartFile) {
268
+                            $partStorage->unlink($internalPartPath);
269
+                        }
270
+                        throw new FileLocked($e->getMessage(), $e->getCode(), $e);
271
+                    }
272
+                }
273
+
274
+                // rename to correct path
275
+                try {
276
+                    $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
277
+                    $fileExists = $storage->file_exists($internalPath);
278
+                    if ($renameOkay === false || $fileExists === false) {
279
+                        \OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
280
+                        throw new Exception('Could not rename part file to final file');
281
+                    }
282
+                } catch (ForbiddenException $ex) {
283
+                    throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
284
+                } catch (\Exception $e) {
285
+                    $partStorage->unlink($internalPartPath);
286
+                    $this->convertToSabreException($e);
287
+                }
288
+            }
289
+
290
+            // since we skipped the view we need to scan and emit the hooks ourselves
291
+            $storage->getUpdater()->update($internalPath);
292
+
293
+            try {
294
+                $this->changeLock(ILockingProvider::LOCK_SHARED);
295
+            } catch (LockedException $e) {
296
+                throw new FileLocked($e->getMessage(), $e->getCode(), $e);
297
+            }
298
+
299
+            // allow sync clients to send the mtime along in a header
300
+            if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
301
+                $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
302
+                if ($this->fileView->touch($this->path, $mtime)) {
303
+                    $this->header('X-OC-MTime: accepted');
304
+                }
305
+            }
306
+
307
+            $fileInfoUpdate = [
308
+                'upload_time' => time()
309
+            ];
310
+
311
+            // allow sync clients to send the creation time along in a header
312
+            if (isset($this->request->server['HTTP_X_OC_CTIME'])) {
313
+                $ctime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_CTIME']);
314
+                $fileInfoUpdate['creation_time'] = $ctime;
315
+                $this->header('X-OC-CTime: accepted');
316
+            }
317
+
318
+            $this->fileView->putFileInfo($this->path, $fileInfoUpdate);
319
+
320
+            if ($view) {
321
+                $this->emitPostHooks($exists);
322
+            }
323
+
324
+            $this->refreshInfo();
325
+
326
+            if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
327
+                $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
328
+                $this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
329
+                $this->refreshInfo();
330
+            } else if ($this->getChecksum() !== null && $this->getChecksum() !== '') {
331
+                $this->fileView->putFileInfo($this->path, ['checksum' => '']);
332
+                $this->refreshInfo();
333
+            }
334
+
335
+        } catch (StorageNotAvailableException $e) {
336
+            throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
337
+        }
338
+
339
+        return '"' . $this->info->getEtag() . '"';
340
+    }
341
+
342
+    private function getPartFileBasePath($path) {
343
+        $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
344
+        if ($partFileInStorage) {
345
+            return $path;
346
+        } else {
347
+            return md5($path); // will place it in the root of the view with a unique name
348
+        }
349
+    }
350
+
351
+    /**
352
+     * @param string $path
353
+     */
354
+    private function emitPreHooks($exists, $path = null) {
355
+        if (is_null($path)) {
356
+            $path = $this->path;
357
+        }
358
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
359
+        $run = true;
360
+
361
+        if (!$exists) {
362
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, [
363
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
364
+                \OC\Files\Filesystem::signal_param_run => &$run,
365
+            ]);
366
+        } else {
367
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, [
368
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
369
+                \OC\Files\Filesystem::signal_param_run => &$run,
370
+            ]);
371
+        }
372
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, [
373
+            \OC\Files\Filesystem::signal_param_path => $hookPath,
374
+            \OC\Files\Filesystem::signal_param_run => &$run,
375
+        ]);
376
+        return $run;
377
+    }
378
+
379
+    /**
380
+     * @param string $path
381
+     */
382
+    private function emitPostHooks($exists, $path = null) {
383
+        if (is_null($path)) {
384
+            $path = $this->path;
385
+        }
386
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
387
+        if (!$exists) {
388
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [
389
+                \OC\Files\Filesystem::signal_param_path => $hookPath
390
+            ]);
391
+        } else {
392
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, [
393
+                \OC\Files\Filesystem::signal_param_path => $hookPath
394
+            ]);
395
+        }
396
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, [
397
+            \OC\Files\Filesystem::signal_param_path => $hookPath
398
+        ]);
399
+    }
400
+
401
+    /**
402
+     * Returns the data
403
+     *
404
+     * @return resource
405
+     * @throws Forbidden
406
+     * @throws ServiceUnavailable
407
+     */
408
+    public function get() {
409
+        //throw exception if encryption is disabled but files are still encrypted
410
+        try {
411
+            if (!$this->info->isReadable()) {
412
+                // do a if the file did not exist
413
+                throw new NotFound();
414
+            }
415
+            try {
416
+                $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
417
+            } catch (\Exception $e) {
418
+                $this->convertToSabreException($e);
419
+            }
420
+            if ($res === false) {
421
+                throw new ServiceUnavailable("Could not open file");
422
+            }
423
+            return $res;
424
+        } catch (GenericEncryptionException $e) {
425
+            // returning 503 will allow retry of the operation at a later point in time
426
+            throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
427
+        } catch (StorageNotAvailableException $e) {
428
+            throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
429
+        } catch (ForbiddenException $ex) {
430
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
431
+        } catch (LockedException $e) {
432
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
433
+        }
434
+    }
435
+
436
+    /**
437
+     * Delete the current file
438
+     *
439
+     * @throws Forbidden
440
+     * @throws ServiceUnavailable
441
+     */
442
+    public function delete() {
443
+        if (!$this->info->isDeletable()) {
444
+            throw new Forbidden();
445
+        }
446
+
447
+        try {
448
+            if (!$this->fileView->unlink($this->path)) {
449
+                // assume it wasn't possible to delete due to permissions
450
+                throw new Forbidden();
451
+            }
452
+        } catch (StorageNotAvailableException $e) {
453
+            throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
454
+        } catch (ForbiddenException $ex) {
455
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
456
+        } catch (LockedException $e) {
457
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
458
+        }
459
+    }
460
+
461
+    /**
462
+     * Returns the mime-type for a file
463
+     *
464
+     * If null is returned, we'll assume application/octet-stream
465
+     *
466
+     * @return string
467
+     */
468
+    public function getContentType() {
469
+        $mimeType = $this->info->getMimetype();
470
+
471
+        // PROPFIND needs to return the correct mime type, for consistency with the web UI
472
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
473
+            return $mimeType;
474
+        }
475
+        return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
476
+    }
477
+
478
+    /**
479
+     * @return array|false
480
+     */
481
+    public function getDirectDownload() {
482
+        if (\OCP\App::isEnabled('encryption')) {
483
+            return [];
484
+        }
485
+        /** @var \OCP\Files\Storage $storage */
486
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
487
+        if (is_null($storage)) {
488
+            return [];
489
+        }
490
+
491
+        return $storage->getDirectDownload($internalPath);
492
+    }
493
+
494
+    /**
495
+     * @param resource $data
496
+     * @return null|string
497
+     * @throws Exception
498
+     * @throws BadRequest
499
+     * @throws NotImplemented
500
+     * @throws ServiceUnavailable
501
+     */
502
+    private function createFileChunked($data) {
503
+        list($path, $name) = \Sabre\Uri\split($this->path);
504
+
505
+        $info = \OC_FileChunking::decodeName($name);
506
+        if (empty($info)) {
507
+            throw new NotImplemented('Invalid chunk name');
508
+        }
509
+
510
+        $chunk_handler = new \OC_FileChunking($info);
511
+        $bytesWritten = $chunk_handler->store($info['index'], $data);
512
+
513
+        //detect aborted upload
514
+        if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
515
+            if (isset($_SERVER['CONTENT_LENGTH'])) {
516
+                $expected = (int)$_SERVER['CONTENT_LENGTH'];
517
+                if ($bytesWritten !== $expected) {
518
+                    $chunk_handler->remove($info['index']);
519
+                    throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $bytesWritten . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
520
+                }
521
+            }
522
+        }
523
+
524
+        if ($chunk_handler->isComplete()) {
525
+            /** @var Storage $storage */
526
+            list($storage,) = $this->fileView->resolvePath($path);
527
+            $needsPartFile = $storage->needsPartFile();
528
+            $partFile = null;
529
+
530
+            $targetPath = $path . '/' . $info['name'];
531
+            /** @var \OC\Files\Storage\Storage $targetStorage */
532
+            list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
533
+
534
+            $exists = $this->fileView->file_exists($targetPath);
535
+
536
+            try {
537
+                $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
538
+
539
+                $this->emitPreHooks($exists, $targetPath);
540
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
541
+                /** @var \OC\Files\Storage\Storage $targetStorage */
542
+                list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
543
+
544
+                if ($needsPartFile) {
545
+                    // we first assembly the target file as a part file
546
+                    $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
547
+                    /** @var \OC\Files\Storage\Storage $targetStorage */
548
+                    list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
549
+
550
+
551
+                    $chunk_handler->file_assemble($partStorage, $partInternalPath);
552
+
553
+                    // here is the final atomic rename
554
+                    $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
555
+                    $fileExists = $targetStorage->file_exists($targetInternalPath);
556
+                    if ($renameOkay === false || $fileExists === false) {
557
+                        \OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
558
+                        // only delete if an error occurred and the target file was already created
559
+                        if ($fileExists) {
560
+                            // set to null to avoid double-deletion when handling exception
561
+                            // stray part file
562
+                            $partFile = null;
563
+                            $targetStorage->unlink($targetInternalPath);
564
+                        }
565
+                        $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
566
+                        throw new Exception('Could not rename part file assembled from chunks');
567
+                    }
568
+                } else {
569
+                    // assemble directly into the final file
570
+                    $chunk_handler->file_assemble($targetStorage, $targetInternalPath);
571
+                }
572
+
573
+                // allow sync clients to send the mtime along in a header
574
+                if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
575
+                    $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
576
+                    if ($targetStorage->touch($targetInternalPath, $mtime)) {
577
+                        $this->header('X-OC-MTime: accepted');
578
+                    }
579
+                }
580
+
581
+                // since we skipped the view we need to scan and emit the hooks ourselves
582
+                $targetStorage->getUpdater()->update($targetInternalPath);
583
+
584
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
585
+
586
+                $this->emitPostHooks($exists, $targetPath);
587
+
588
+                // FIXME: should call refreshInfo but can't because $this->path is not the of the final file
589
+                $info = $this->fileView->getFileInfo($targetPath);
590
+
591
+                if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
592
+                    $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
593
+                    $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
594
+                } else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
595
+                    $this->fileView->putFileInfo($this->path, ['checksum' => '']);
596
+                }
597
+
598
+                $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
599
+
600
+                return $info->getEtag();
601
+            } catch (\Exception $e) {
602
+                if ($partFile !== null) {
603
+                    $targetStorage->unlink($targetInternalPath);
604
+                }
605
+                $this->convertToSabreException($e);
606
+            }
607
+        }
608
+
609
+        return null;
610
+    }
611
+
612
+    /**
613
+     * Convert the given exception to a SabreException instance
614
+     *
615
+     * @param \Exception $e
616
+     *
617
+     * @throws \Sabre\DAV\Exception
618
+     */
619
+    private function convertToSabreException(\Exception $e) {
620
+        if ($e instanceof \Sabre\DAV\Exception) {
621
+            throw $e;
622
+        }
623
+        if ($e instanceof NotPermittedException) {
624
+            // a more general case - due to whatever reason the content could not be written
625
+            throw new Forbidden($e->getMessage(), 0, $e);
626
+        }
627
+        if ($e instanceof ForbiddenException) {
628
+            // the path for the file was forbidden
629
+            throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
630
+        }
631
+        if ($e instanceof EntityTooLargeException) {
632
+            // the file is too big to be stored
633
+            throw new EntityTooLarge($e->getMessage(), 0, $e);
634
+        }
635
+        if ($e instanceof InvalidContentException) {
636
+            // the file content is not permitted
637
+            throw new UnsupportedMediaType($e->getMessage(), 0, $e);
638
+        }
639
+        if ($e instanceof InvalidPathException) {
640
+            // the path for the file was not valid
641
+            // TODO: find proper http status code for this case
642
+            throw new Forbidden($e->getMessage(), 0, $e);
643
+        }
644
+        if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
645
+            // the file is currently being written to by another process
646
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
647
+        }
648
+        if ($e instanceof GenericEncryptionException) {
649
+            // returning 503 will allow retry of the operation at a later point in time
650
+            throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
651
+        }
652
+        if ($e instanceof StorageNotAvailableException) {
653
+            throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
654
+        }
655
+        if ($e instanceof NotFoundException) {
656
+            throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
657
+        }
658
+
659
+        throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
660
+    }
661
+
662
+    /**
663
+     * Get the checksum for this file
664
+     *
665
+     * @return string
666
+     */
667
+    public function getChecksum() {
668
+        return $this->info->getChecksum();
669
+    }
670
+
671
+    protected function header($string) {
672
+        \header($string);
673
+    }
674 674
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/CommentPropertiesPlugin.php 1 patch
Indentation   +127 added lines, -127 removed lines patch added patch discarded remove patch
@@ -32,132 +32,132 @@
 block discarded – undo
32 32
 
33 33
 class CommentPropertiesPlugin extends ServerPlugin {
34 34
 
35
-	const PROPERTY_NAME_HREF   = '{http://owncloud.org/ns}comments-href';
36
-	const PROPERTY_NAME_COUNT  = '{http://owncloud.org/ns}comments-count';
37
-	const PROPERTY_NAME_UNREAD = '{http://owncloud.org/ns}comments-unread';
38
-
39
-	/** @var  \Sabre\DAV\Server */
40
-	protected $server;
41
-
42
-	/** @var ICommentsManager */
43
-	private $commentsManager;
44
-
45
-	/** @var IUserSession */
46
-	private $userSession;
47
-
48
-	private $cachedUnreadCount = [];
49
-
50
-	private $cachedFolders = [];
51
-
52
-	public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) {
53
-		$this->commentsManager = $commentsManager;
54
-		$this->userSession = $userSession;
55
-	}
56
-
57
-	/**
58
-	 * This initializes the plugin.
59
-	 *
60
-	 * This function is called by Sabre\DAV\Server, after
61
-	 * addPlugin is called.
62
-	 *
63
-	 * This method should set up the required event subscriptions.
64
-	 *
65
-	 * @param \Sabre\DAV\Server $server
66
-	 * @return void
67
-	 */
68
-	function initialize(\Sabre\DAV\Server $server) {
69
-		$this->server = $server;
70
-		$this->server->on('propFind', [$this, 'handleGetProperties']);
71
-	}
72
-
73
-	/**
74
-	 * Adds tags and favorites properties to the response,
75
-	 * if requested.
76
-	 *
77
-	 * @param PropFind $propFind
78
-	 * @param \Sabre\DAV\INode $node
79
-	 * @return void
80
-	 */
81
-	public function handleGetProperties(
82
-		PropFind $propFind,
83
-		\Sabre\DAV\INode $node
84
-	) {
85
-		if (!($node instanceof File) && !($node instanceof Directory)) {
86
-			return;
87
-		}
88
-
89
-		// need prefetch ?
90
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Directory
91
-			&& $propFind->getDepth() !== 0
92
-			&& !is_null($propFind->getStatus(self::PROPERTY_NAME_UNREAD))
93
-		) {
94
-			$unreadCounts = $this->commentsManager->getNumberOfUnreadCommentsForFolder($node->getId(), $this->userSession->getUser());
95
-			$this->cachedFolders[] = $node->getPath();
96
-			foreach ($unreadCounts as $id => $count) {
97
-				$this->cachedUnreadCount[$id] = $count;
98
-			}
99
-		}
100
-
101
-		$propFind->handle(self::PROPERTY_NAME_COUNT, function() use ($node) {
102
-			return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId());
103
-		});
104
-
105
-		$propFind->handle(self::PROPERTY_NAME_HREF, function() use ($node) {
106
-			return $this->getCommentsLink($node);
107
-		});
108
-
109
-		$propFind->handle(self::PROPERTY_NAME_UNREAD, function() use ($node) {
110
-			if (isset($this->cachedUnreadCount[$node->getId()])) {
111
-				return $this->cachedUnreadCount[$node->getId()];
112
-			} else {
113
-				list($parentPath,) = \Sabre\Uri\split($node->getPath());
114
-				if ($parentPath === '') {
115
-					$parentPath = '/';
116
-				}
117
-				// if we already cached the folder this file is in we know there are no comments for this file
118
-				if (array_search($parentPath, $this->cachedFolders) === false) {
119
-					return 0;
120
-				} else {
121
-					return $this->getUnreadCount($node);
122
-				}
123
-			}
124
-		});
125
-	}
126
-
127
-	/**
128
-	 * returns a reference to the comments node
129
-	 *
130
-	 * @param Node $node
131
-	 * @return mixed|string
132
-	 */
133
-	public function getCommentsLink(Node $node) {
134
-		$href =  $this->server->getBaseUri();
135
-		$entryPoint = strpos($href, '/remote.php/');
136
-		if($entryPoint === false) {
137
-			// in case we end up somewhere else, unexpectedly.
138
-			return null;
139
-		}
140
-		$commentsPart = 'dav/comments/files/' . rawurldecode($node->getId());
141
-		$href = substr_replace($href, $commentsPart, $entryPoint + strlen('/remote.php/'));
142
-		return $href;
143
-	}
144
-
145
-	/**
146
-	 * returns the number of unread comments for the currently logged in user
147
-	 * on the given file or directory node
148
-	 *
149
-	 * @param Node $node
150
-	 * @return Int|null
151
-	 */
152
-	public function getUnreadCount(Node $node) {
153
-		$user = $this->userSession->getUser();
154
-		if(is_null($user)) {
155
-			return null;
156
-		}
157
-
158
-		$lastRead = $this->commentsManager->getReadMark('files', (string)$node->getId(), $user);
159
-
160
-		return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId(), $lastRead);
161
-	}
35
+    const PROPERTY_NAME_HREF   = '{http://owncloud.org/ns}comments-href';
36
+    const PROPERTY_NAME_COUNT  = '{http://owncloud.org/ns}comments-count';
37
+    const PROPERTY_NAME_UNREAD = '{http://owncloud.org/ns}comments-unread';
38
+
39
+    /** @var  \Sabre\DAV\Server */
40
+    protected $server;
41
+
42
+    /** @var ICommentsManager */
43
+    private $commentsManager;
44
+
45
+    /** @var IUserSession */
46
+    private $userSession;
47
+
48
+    private $cachedUnreadCount = [];
49
+
50
+    private $cachedFolders = [];
51
+
52
+    public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) {
53
+        $this->commentsManager = $commentsManager;
54
+        $this->userSession = $userSession;
55
+    }
56
+
57
+    /**
58
+     * This initializes the plugin.
59
+     *
60
+     * This function is called by Sabre\DAV\Server, after
61
+     * addPlugin is called.
62
+     *
63
+     * This method should set up the required event subscriptions.
64
+     *
65
+     * @param \Sabre\DAV\Server $server
66
+     * @return void
67
+     */
68
+    function initialize(\Sabre\DAV\Server $server) {
69
+        $this->server = $server;
70
+        $this->server->on('propFind', [$this, 'handleGetProperties']);
71
+    }
72
+
73
+    /**
74
+     * Adds tags and favorites properties to the response,
75
+     * if requested.
76
+     *
77
+     * @param PropFind $propFind
78
+     * @param \Sabre\DAV\INode $node
79
+     * @return void
80
+     */
81
+    public function handleGetProperties(
82
+        PropFind $propFind,
83
+        \Sabre\DAV\INode $node
84
+    ) {
85
+        if (!($node instanceof File) && !($node instanceof Directory)) {
86
+            return;
87
+        }
88
+
89
+        // need prefetch ?
90
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Directory
91
+            && $propFind->getDepth() !== 0
92
+            && !is_null($propFind->getStatus(self::PROPERTY_NAME_UNREAD))
93
+        ) {
94
+            $unreadCounts = $this->commentsManager->getNumberOfUnreadCommentsForFolder($node->getId(), $this->userSession->getUser());
95
+            $this->cachedFolders[] = $node->getPath();
96
+            foreach ($unreadCounts as $id => $count) {
97
+                $this->cachedUnreadCount[$id] = $count;
98
+            }
99
+        }
100
+
101
+        $propFind->handle(self::PROPERTY_NAME_COUNT, function() use ($node) {
102
+            return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId());
103
+        });
104
+
105
+        $propFind->handle(self::PROPERTY_NAME_HREF, function() use ($node) {
106
+            return $this->getCommentsLink($node);
107
+        });
108
+
109
+        $propFind->handle(self::PROPERTY_NAME_UNREAD, function() use ($node) {
110
+            if (isset($this->cachedUnreadCount[$node->getId()])) {
111
+                return $this->cachedUnreadCount[$node->getId()];
112
+            } else {
113
+                list($parentPath,) = \Sabre\Uri\split($node->getPath());
114
+                if ($parentPath === '') {
115
+                    $parentPath = '/';
116
+                }
117
+                // if we already cached the folder this file is in we know there are no comments for this file
118
+                if (array_search($parentPath, $this->cachedFolders) === false) {
119
+                    return 0;
120
+                } else {
121
+                    return $this->getUnreadCount($node);
122
+                }
123
+            }
124
+        });
125
+    }
126
+
127
+    /**
128
+     * returns a reference to the comments node
129
+     *
130
+     * @param Node $node
131
+     * @return mixed|string
132
+     */
133
+    public function getCommentsLink(Node $node) {
134
+        $href =  $this->server->getBaseUri();
135
+        $entryPoint = strpos($href, '/remote.php/');
136
+        if($entryPoint === false) {
137
+            // in case we end up somewhere else, unexpectedly.
138
+            return null;
139
+        }
140
+        $commentsPart = 'dav/comments/files/' . rawurldecode($node->getId());
141
+        $href = substr_replace($href, $commentsPart, $entryPoint + strlen('/remote.php/'));
142
+        return $href;
143
+    }
144
+
145
+    /**
146
+     * returns the number of unread comments for the currently logged in user
147
+     * on the given file or directory node
148
+     *
149
+     * @param Node $node
150
+     * @return Int|null
151
+     */
152
+    public function getUnreadCount(Node $node) {
153
+        $user = $this->userSession->getUser();
154
+        if(is_null($user)) {
155
+            return null;
156
+        }
157
+
158
+        $lastRead = $this->commentsManager->getReadMark('files', (string)$node->getId(), $user);
159
+
160
+        return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId(), $lastRead);
161
+    }
162 162
 
163 163
 }
Please login to merge, or discard this patch.