Completed
Pull Request — master (#9148)
by Robin
42:31 queued 20:12
created
apps/dav/lib/Connector/Sabre/File.php 1 patch
Indentation   +537 added lines, -537 removed lines patch added patch discarded remove patch
@@ -65,542 +65,542 @@
 block discarded – undo
65 65
 
66 66
 class File extends Node implements IFile {
67 67
 
68
-	protected $request;
69
-
70
-	/**
71
-	 * Sets up the node, expects a full path name
72
-	 *
73
-	 * @param \OC\Files\View $view
74
-	 * @param \OCP\Files\FileInfo $info
75
-	 * @param \OCP\Share\IManager $shareManager
76
-	 * @param \OC\AppFramework\Http\Request $request
77
-	 */
78
-	public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
79
-		parent::__construct($view, $info, $shareManager);
80
-
81
-		if (isset($request)) {
82
-			$this->request = $request;
83
-		} else {
84
-			$this->request = \OC::$server->getRequest();
85
-		}
86
-	}
87
-
88
-	/**
89
-	 * Updates the data
90
-	 *
91
-	 * The data argument is a readable stream resource.
92
-	 *
93
-	 * After a successful put operation, you may choose to return an ETag. The
94
-	 * etag must always be surrounded by double-quotes. These quotes must
95
-	 * appear in the actual string you're returning.
96
-	 *
97
-	 * Clients may use the ETag from a PUT request to later on make sure that
98
-	 * when they update the file, the contents haven't changed in the mean
99
-	 * time.
100
-	 *
101
-	 * If you don't plan to store the file byte-by-byte, and you return a
102
-	 * different object on a subsequent GET you are strongly recommended to not
103
-	 * return an ETag, and just return null.
104
-	 *
105
-	 * @param resource $data
106
-	 *
107
-	 * @throws Forbidden
108
-	 * @throws UnsupportedMediaType
109
-	 * @throws BadRequest
110
-	 * @throws Exception
111
-	 * @throws EntityTooLarge
112
-	 * @throws ServiceUnavailable
113
-	 * @throws FileLocked
114
-	 * @return string|null
115
-	 */
116
-	public function put($data) {
117
-		try {
118
-			$exists = $this->fileView->file_exists($this->path);
119
-			if ($this->info && $exists && !$this->info->isUpdateable()) {
120
-				throw new Forbidden();
121
-			}
122
-		} catch (StorageNotAvailableException $e) {
123
-			throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
124
-		}
125
-
126
-		// verify path of the target
127
-		$this->verifyPath();
128
-
129
-		// chunked handling
130
-		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
131
-			try {
132
-				return $this->createFileChunked($data);
133
-			} catch (\Exception $e) {
134
-				$this->convertToSabreException($e);
135
-			}
136
-		}
137
-
138
-		list($partStorage) = $this->fileView->resolvePath($this->path);
139
-		$needsPartFile = $this->needsPartFile($partStorage) && (strlen($this->path) > 1);
140
-
141
-		if ($needsPartFile) {
142
-			// mark file as partial while uploading (ignored by the scanner)
143
-			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
144
-		} else {
145
-			// upload file directly as the final path
146
-			$partFilePath = $this->path;
147
-
148
-			$this->emitPreHooks($exists);
149
-		}
150
-
151
-		// the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
152
-		/** @var \OC\Files\Storage\Storage $partStorage */
153
-		list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
154
-		/** @var \OC\Files\Storage\Storage $storage */
155
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
156
-		try {
157
-			$target = $partStorage->fopen($internalPartPath, 'wb');
158
-			if ($target === false) {
159
-				\OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::fopen() failed', \OCP\Util::ERROR);
160
-				// because we have no clue about the cause we can only throw back a 500/Internal Server Error
161
-				throw new Exception('Could not write file contents');
162
-			}
163
-			list($count, $result) = \OC_Helper::streamCopy($data, $target);
164
-			fclose($target);
165
-
166
-			if ($result === false) {
167
-				$expected = -1;
168
-				if (isset($_SERVER['CONTENT_LENGTH'])) {
169
-					$expected = $_SERVER['CONTENT_LENGTH'];
170
-				}
171
-				throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
172
-			}
173
-
174
-			// if content length is sent by client:
175
-			// double check if the file was fully received
176
-			// compare expected and actual size
177
-			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
178
-				$expected = (int) $_SERVER['CONTENT_LENGTH'];
179
-				if ($count !== $expected) {
180
-					throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
181
-				}
182
-			}
183
-
184
-		} catch (\Exception $e) {
185
-			if ($needsPartFile) {
186
-				$partStorage->unlink($internalPartPath);
187
-			}
188
-			$this->convertToSabreException($e);
189
-		}
190
-
191
-		try {
192
-			$view = \OC\Files\Filesystem::getView();
193
-			$run = ($view && $needsPartFile) ? $this->emitPreHooks($exists) : true;
194
-
195
-			try {
196
-				$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
197
-			} catch (LockedException $e) {
198
-				if ($needsPartFile) {
199
-					$partStorage->unlink($internalPartPath);
200
-				}
201
-				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
202
-			}
203
-
204
-			if ($needsPartFile) {
205
-				// rename to correct path
206
-				try {
207
-					if ($run) {
208
-						$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
209
-						$fileExists = $storage->file_exists($internalPath);
210
-					}
211
-					if (!$run || $renameOkay === false || $fileExists === false) {
212
-						\OCP\Util::writeLog('webdav', 'renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: '  . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', \OCP\Util::ERROR);
213
-						throw new Exception('Could not rename part file to final file');
214
-					}
215
-				} catch (ForbiddenException $ex) {
216
-					throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
217
-				} catch (\Exception $e) {
218
-					$partStorage->unlink($internalPartPath);
219
-					$this->convertToSabreException($e);
220
-				}
221
-			}
222
-
223
-			// since we skipped the view we need to scan and emit the hooks ourselves
224
-			$storage->getUpdater()->update($internalPath);
225
-
226
-			try {
227
-				$this->changeLock(ILockingProvider::LOCK_SHARED);
228
-			} catch (LockedException $e) {
229
-				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
230
-			}
231
-
232
-			// allow sync clients to send the mtime along in a header
233
-			if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
234
-				$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
235
-				if ($this->fileView->touch($this->path, $mtime)) {
236
-					$this->header('X-OC-MTime: accepted');
237
-				}
238
-			}
68
+    protected $request;
69
+
70
+    /**
71
+     * Sets up the node, expects a full path name
72
+     *
73
+     * @param \OC\Files\View $view
74
+     * @param \OCP\Files\FileInfo $info
75
+     * @param \OCP\Share\IManager $shareManager
76
+     * @param \OC\AppFramework\Http\Request $request
77
+     */
78
+    public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
79
+        parent::__construct($view, $info, $shareManager);
80
+
81
+        if (isset($request)) {
82
+            $this->request = $request;
83
+        } else {
84
+            $this->request = \OC::$server->getRequest();
85
+        }
86
+    }
87
+
88
+    /**
89
+     * Updates the data
90
+     *
91
+     * The data argument is a readable stream resource.
92
+     *
93
+     * After a successful put operation, you may choose to return an ETag. The
94
+     * etag must always be surrounded by double-quotes. These quotes must
95
+     * appear in the actual string you're returning.
96
+     *
97
+     * Clients may use the ETag from a PUT request to later on make sure that
98
+     * when they update the file, the contents haven't changed in the mean
99
+     * time.
100
+     *
101
+     * If you don't plan to store the file byte-by-byte, and you return a
102
+     * different object on a subsequent GET you are strongly recommended to not
103
+     * return an ETag, and just return null.
104
+     *
105
+     * @param resource $data
106
+     *
107
+     * @throws Forbidden
108
+     * @throws UnsupportedMediaType
109
+     * @throws BadRequest
110
+     * @throws Exception
111
+     * @throws EntityTooLarge
112
+     * @throws ServiceUnavailable
113
+     * @throws FileLocked
114
+     * @return string|null
115
+     */
116
+    public function put($data) {
117
+        try {
118
+            $exists = $this->fileView->file_exists($this->path);
119
+            if ($this->info && $exists && !$this->info->isUpdateable()) {
120
+                throw new Forbidden();
121
+            }
122
+        } catch (StorageNotAvailableException $e) {
123
+            throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
124
+        }
125
+
126
+        // verify path of the target
127
+        $this->verifyPath();
128
+
129
+        // chunked handling
130
+        if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
131
+            try {
132
+                return $this->createFileChunked($data);
133
+            } catch (\Exception $e) {
134
+                $this->convertToSabreException($e);
135
+            }
136
+        }
137
+
138
+        list($partStorage) = $this->fileView->resolvePath($this->path);
139
+        $needsPartFile = $this->needsPartFile($partStorage) && (strlen($this->path) > 1);
140
+
141
+        if ($needsPartFile) {
142
+            // mark file as partial while uploading (ignored by the scanner)
143
+            $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
144
+        } else {
145
+            // upload file directly as the final path
146
+            $partFilePath = $this->path;
147
+
148
+            $this->emitPreHooks($exists);
149
+        }
150
+
151
+        // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
152
+        /** @var \OC\Files\Storage\Storage $partStorage */
153
+        list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
154
+        /** @var \OC\Files\Storage\Storage $storage */
155
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
156
+        try {
157
+            $target = $partStorage->fopen($internalPartPath, 'wb');
158
+            if ($target === false) {
159
+                \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::fopen() failed', \OCP\Util::ERROR);
160
+                // because we have no clue about the cause we can only throw back a 500/Internal Server Error
161
+                throw new Exception('Could not write file contents');
162
+            }
163
+            list($count, $result) = \OC_Helper::streamCopy($data, $target);
164
+            fclose($target);
165
+
166
+            if ($result === false) {
167
+                $expected = -1;
168
+                if (isset($_SERVER['CONTENT_LENGTH'])) {
169
+                    $expected = $_SERVER['CONTENT_LENGTH'];
170
+                }
171
+                throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
172
+            }
173
+
174
+            // if content length is sent by client:
175
+            // double check if the file was fully received
176
+            // compare expected and actual size
177
+            if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
178
+                $expected = (int) $_SERVER['CONTENT_LENGTH'];
179
+                if ($count !== $expected) {
180
+                    throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
181
+                }
182
+            }
183
+
184
+        } catch (\Exception $e) {
185
+            if ($needsPartFile) {
186
+                $partStorage->unlink($internalPartPath);
187
+            }
188
+            $this->convertToSabreException($e);
189
+        }
190
+
191
+        try {
192
+            $view = \OC\Files\Filesystem::getView();
193
+            $run = ($view && $needsPartFile) ? $this->emitPreHooks($exists) : true;
194
+
195
+            try {
196
+                $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
197
+            } catch (LockedException $e) {
198
+                if ($needsPartFile) {
199
+                    $partStorage->unlink($internalPartPath);
200
+                }
201
+                throw new FileLocked($e->getMessage(), $e->getCode(), $e);
202
+            }
203
+
204
+            if ($needsPartFile) {
205
+                // rename to correct path
206
+                try {
207
+                    if ($run) {
208
+                        $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
209
+                        $fileExists = $storage->file_exists($internalPath);
210
+                    }
211
+                    if (!$run || $renameOkay === false || $fileExists === false) {
212
+                        \OCP\Util::writeLog('webdav', 'renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: '  . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', \OCP\Util::ERROR);
213
+                        throw new Exception('Could not rename part file to final file');
214
+                    }
215
+                } catch (ForbiddenException $ex) {
216
+                    throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
217
+                } catch (\Exception $e) {
218
+                    $partStorage->unlink($internalPartPath);
219
+                    $this->convertToSabreException($e);
220
+                }
221
+            }
222
+
223
+            // since we skipped the view we need to scan and emit the hooks ourselves
224
+            $storage->getUpdater()->update($internalPath);
225
+
226
+            try {
227
+                $this->changeLock(ILockingProvider::LOCK_SHARED);
228
+            } catch (LockedException $e) {
229
+                throw new FileLocked($e->getMessage(), $e->getCode(), $e);
230
+            }
231
+
232
+            // allow sync clients to send the mtime along in a header
233
+            if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
234
+                $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
235
+                if ($this->fileView->touch($this->path, $mtime)) {
236
+                    $this->header('X-OC-MTime: accepted');
237
+                }
238
+            }
239 239
 					
240
-			if ($view) {
241
-				$this->emitPostHooks($exists);
242
-			}
243
-
244
-			$this->refreshInfo();
245
-
246
-			if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
247
-				$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
248
-				$this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
249
-				$this->refreshInfo();
250
-			} else if ($this->getChecksum() !== null && $this->getChecksum() !== '') {
251
-				$this->fileView->putFileInfo($this->path, ['checksum' => '']);
252
-				$this->refreshInfo();
253
-			}
254
-
255
-		} catch (StorageNotAvailableException $e) {
256
-			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
257
-		}
258
-
259
-		return '"' . $this->info->getEtag() . '"';
260
-	}
261
-
262
-	private function getPartFileBasePath($path) {
263
-		$partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
264
-		if ($partFileInStorage) {
265
-			return $path;
266
-		} else {
267
-			return md5($path); // will place it in the root of the view with a unique name
268
-		}
269
-	}
270
-
271
-	/**
272
-	 * @param string $path
273
-	 */
274
-	private function emitPreHooks($exists, $path = null) {
275
-		if (is_null($path)) {
276
-			$path = $this->path;
277
-		}
278
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
279
-		$run = true;
280
-
281
-		if (!$exists) {
282
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, array(
283
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
284
-				\OC\Files\Filesystem::signal_param_run => &$run,
285
-			));
286
-		} else {
287
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, array(
288
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
289
-				\OC\Files\Filesystem::signal_param_run => &$run,
290
-			));
291
-		}
292
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, array(
293
-			\OC\Files\Filesystem::signal_param_path => $hookPath,
294
-			\OC\Files\Filesystem::signal_param_run => &$run,
295
-		));
296
-		return $run;
297
-	}
298
-
299
-	/**
300
-	 * @param string $path
301
-	 */
302
-	private function emitPostHooks($exists, $path = null) {
303
-		if (is_null($path)) {
304
-			$path = $this->path;
305
-		}
306
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
307
-		if (!$exists) {
308
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, array(
309
-				\OC\Files\Filesystem::signal_param_path => $hookPath
310
-			));
311
-		} else {
312
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, array(
313
-				\OC\Files\Filesystem::signal_param_path => $hookPath
314
-			));
315
-		}
316
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, array(
317
-			\OC\Files\Filesystem::signal_param_path => $hookPath
318
-		));
319
-	}
320
-
321
-	/**
322
-	 * Returns the data
323
-	 *
324
-	 * @return resource
325
-	 * @throws Forbidden
326
-	 * @throws ServiceUnavailable
327
-	 */
328
-	public function get() {
329
-		//throw exception if encryption is disabled but files are still encrypted
330
-		try {
331
-			if (!$this->info->isReadable()) {
332
-				// do a if the file did not exist
333
-				throw new NotFound();
334
-			}
335
-			try {
336
-				$res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
337
-			} catch (\Exception $e) {
338
-				$this->convertToSabreException($e);
339
-			}
340
-			if ($res === false) {
341
-				throw new ServiceUnavailable("Could not open file");
342
-			}
343
-			return $res;
344
-		} catch (GenericEncryptionException $e) {
345
-			// returning 503 will allow retry of the operation at a later point in time
346
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
347
-		} catch (StorageNotAvailableException $e) {
348
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
349
-		} catch (ForbiddenException $ex) {
350
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
351
-		} catch (LockedException $e) {
352
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
353
-		}
354
-	}
355
-
356
-	/**
357
-	 * Delete the current file
358
-	 *
359
-	 * @throws Forbidden
360
-	 * @throws ServiceUnavailable
361
-	 */
362
-	public function delete() {
363
-		if (!$this->info->isDeletable()) {
364
-			throw new Forbidden();
365
-		}
366
-
367
-		try {
368
-			if (!$this->fileView->unlink($this->path)) {
369
-				// assume it wasn't possible to delete due to permissions
370
-				throw new Forbidden();
371
-			}
372
-		} catch (StorageNotAvailableException $e) {
373
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
374
-		} catch (ForbiddenException $ex) {
375
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
376
-		} catch (LockedException $e) {
377
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
378
-		}
379
-	}
380
-
381
-	/**
382
-	 * Returns the mime-type for a file
383
-	 *
384
-	 * If null is returned, we'll assume application/octet-stream
385
-	 *
386
-	 * @return string
387
-	 */
388
-	public function getContentType() {
389
-		$mimeType = $this->info->getMimetype();
390
-
391
-		// PROPFIND needs to return the correct mime type, for consistency with the web UI
392
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
393
-			return $mimeType;
394
-		}
395
-		return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
396
-	}
397
-
398
-	/**
399
-	 * @return array|false
400
-	 */
401
-	public function getDirectDownload() {
402
-		if (\OCP\App::isEnabled('encryption')) {
403
-			return [];
404
-		}
405
-		/** @var \OCP\Files\Storage $storage */
406
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
407
-		if (is_null($storage)) {
408
-			return [];
409
-		}
410
-
411
-		return $storage->getDirectDownload($internalPath);
412
-	}
413
-
414
-	/**
415
-	 * @param resource $data
416
-	 * @return null|string
417
-	 * @throws Exception
418
-	 * @throws BadRequest
419
-	 * @throws NotImplemented
420
-	 * @throws ServiceUnavailable
421
-	 */
422
-	private function createFileChunked($data) {
423
-		list($path, $name) = \Sabre\Uri\split($this->path);
424
-
425
-		$info = \OC_FileChunking::decodeName($name);
426
-		if (empty($info)) {
427
-			throw new NotImplemented('Invalid chunk name');
428
-		}
429
-
430
-		$chunk_handler = new \OC_FileChunking($info);
431
-		$bytesWritten = $chunk_handler->store($info['index'], $data);
432
-
433
-		//detect aborted upload
434
-		if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
435
-			if (isset($_SERVER['CONTENT_LENGTH'])) {
436
-				$expected = (int) $_SERVER['CONTENT_LENGTH'];
437
-				if ($bytesWritten !== $expected) {
438
-					$chunk_handler->remove($info['index']);
439
-					throw new BadRequest(
440
-						'expected filesize ' . $expected . ' got ' . $bytesWritten);
441
-				}
442
-			}
443
-		}
444
-
445
-		if ($chunk_handler->isComplete()) {
446
-			list($storage,) = $this->fileView->resolvePath($path);
447
-			$needsPartFile = $this->needsPartFile($storage);
448
-			$partFile = null;
449
-
450
-			$targetPath = $path . '/' . $info['name'];
451
-			/** @var \OC\Files\Storage\Storage $targetStorage */
452
-			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
453
-
454
-			$exists = $this->fileView->file_exists($targetPath);
455
-
456
-			try {
457
-				$this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
458
-
459
-				$this->emitPreHooks($exists, $targetPath);
460
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
461
-				/** @var \OC\Files\Storage\Storage $targetStorage */
462
-				list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
463
-
464
-				if ($needsPartFile) {
465
-					// we first assembly the target file as a part file
466
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
467
-					/** @var \OC\Files\Storage\Storage $targetStorage */
468
-					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
469
-
470
-
471
-					$chunk_handler->file_assemble($partStorage, $partInternalPath);
472
-
473
-					// here is the final atomic rename
474
-					$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
475
-					$fileExists = $targetStorage->file_exists($targetInternalPath);
476
-					if ($renameOkay === false || $fileExists === false) {
477
-						\OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::rename() failed', \OCP\Util::ERROR);
478
-						// only delete if an error occurred and the target file was already created
479
-						if ($fileExists) {
480
-							// set to null to avoid double-deletion when handling exception
481
-							// stray part file
482
-							$partFile = null;
483
-							$targetStorage->unlink($targetInternalPath);
484
-						}
485
-						$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
486
-						throw new Exception('Could not rename part file assembled from chunks');
487
-					}
488
-				} else {
489
-					// assemble directly into the final file
490
-					$chunk_handler->file_assemble($targetStorage, $targetInternalPath);
491
-				}
492
-
493
-				// allow sync clients to send the mtime along in a header
494
-				if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
495
-					$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
496
-					if ($targetStorage->touch($targetInternalPath, $mtime)) {
497
-						$this->header('X-OC-MTime: accepted');
498
-					}
499
-				}
500
-
501
-				// since we skipped the view we need to scan and emit the hooks ourselves
502
-				$targetStorage->getUpdater()->update($targetInternalPath);
503
-
504
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
505
-
506
-				$this->emitPostHooks($exists, $targetPath);
507
-
508
-				// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
509
-				$info = $this->fileView->getFileInfo($targetPath);
510
-
511
-				if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
512
-					$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
513
-					$this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
514
-				} else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
515
-					$this->fileView->putFileInfo($this->path, ['checksum' => '']);
516
-				}
517
-
518
-				$this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
519
-
520
-				return $info->getEtag();
521
-			} catch (\Exception $e) {
522
-				if ($partFile !== null) {
523
-					$targetStorage->unlink($targetInternalPath);
524
-				}
525
-				$this->convertToSabreException($e);
526
-			}
527
-		}
528
-
529
-		return null;
530
-	}
531
-
532
-	/**
533
-	 * Returns whether a part file is needed for the given storage
534
-	 * or whether the file can be assembled/uploaded directly on the
535
-	 * target storage.
536
-	 *
537
-	 * @param \OCP\Files\Storage $storage
538
-	 * @return bool true if the storage needs part file handling
539
-	 */
540
-	private function needsPartFile($storage) {
541
-		// TODO: in the future use ChunkHandler provided by storage
542
-		return !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') &&
543
-			!$storage->instanceOfStorage('OC\Files\Storage\OwnCloud') &&
544
-			$storage->needsPartFile();
545
-	}
546
-
547
-	/**
548
-	 * Convert the given exception to a SabreException instance
549
-	 *
550
-	 * @param \Exception $e
551
-	 *
552
-	 * @throws \Sabre\DAV\Exception
553
-	 */
554
-	private function convertToSabreException(\Exception $e) {
555
-		if ($e instanceof \Sabre\DAV\Exception) {
556
-			throw $e;
557
-		}
558
-		if ($e instanceof NotPermittedException) {
559
-			// a more general case - due to whatever reason the content could not be written
560
-			throw new Forbidden($e->getMessage(), 0, $e);
561
-		}
562
-		if ($e instanceof ForbiddenException) {
563
-			// the path for the file was forbidden
564
-			throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
565
-		}
566
-		if ($e instanceof EntityTooLargeException) {
567
-			// the file is too big to be stored
568
-			throw new EntityTooLarge($e->getMessage(), 0, $e);
569
-		}
570
-		if ($e instanceof InvalidContentException) {
571
-			// the file content is not permitted
572
-			throw new UnsupportedMediaType($e->getMessage(), 0, $e);
573
-		}
574
-		if ($e instanceof InvalidPathException) {
575
-			// the path for the file was not valid
576
-			// TODO: find proper http status code for this case
577
-			throw new Forbidden($e->getMessage(), 0, $e);
578
-		}
579
-		if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
580
-			// the file is currently being written to by another process
581
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
582
-		}
583
-		if ($e instanceof GenericEncryptionException) {
584
-			// returning 503 will allow retry of the operation at a later point in time
585
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
586
-		}
587
-		if ($e instanceof StorageNotAvailableException) {
588
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
589
-		}
590
-
591
-		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
592
-	}
593
-
594
-	/**
595
-	 * Get the checksum for this file
596
-	 *
597
-	 * @return string
598
-	 */
599
-	public function getChecksum() {
600
-		return $this->info->getChecksum();
601
-	}
602
-
603
-	protected function header($string) {
604
-		\header($string);
605
-	}
240
+            if ($view) {
241
+                $this->emitPostHooks($exists);
242
+            }
243
+
244
+            $this->refreshInfo();
245
+
246
+            if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
247
+                $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
248
+                $this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
249
+                $this->refreshInfo();
250
+            } else if ($this->getChecksum() !== null && $this->getChecksum() !== '') {
251
+                $this->fileView->putFileInfo($this->path, ['checksum' => '']);
252
+                $this->refreshInfo();
253
+            }
254
+
255
+        } catch (StorageNotAvailableException $e) {
256
+            throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
257
+        }
258
+
259
+        return '"' . $this->info->getEtag() . '"';
260
+    }
261
+
262
+    private function getPartFileBasePath($path) {
263
+        $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
264
+        if ($partFileInStorage) {
265
+            return $path;
266
+        } else {
267
+            return md5($path); // will place it in the root of the view with a unique name
268
+        }
269
+    }
270
+
271
+    /**
272
+     * @param string $path
273
+     */
274
+    private function emitPreHooks($exists, $path = null) {
275
+        if (is_null($path)) {
276
+            $path = $this->path;
277
+        }
278
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
279
+        $run = true;
280
+
281
+        if (!$exists) {
282
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, array(
283
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
284
+                \OC\Files\Filesystem::signal_param_run => &$run,
285
+            ));
286
+        } else {
287
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, array(
288
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
289
+                \OC\Files\Filesystem::signal_param_run => &$run,
290
+            ));
291
+        }
292
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, array(
293
+            \OC\Files\Filesystem::signal_param_path => $hookPath,
294
+            \OC\Files\Filesystem::signal_param_run => &$run,
295
+        ));
296
+        return $run;
297
+    }
298
+
299
+    /**
300
+     * @param string $path
301
+     */
302
+    private function emitPostHooks($exists, $path = null) {
303
+        if (is_null($path)) {
304
+            $path = $this->path;
305
+        }
306
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
307
+        if (!$exists) {
308
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, array(
309
+                \OC\Files\Filesystem::signal_param_path => $hookPath
310
+            ));
311
+        } else {
312
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, array(
313
+                \OC\Files\Filesystem::signal_param_path => $hookPath
314
+            ));
315
+        }
316
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, array(
317
+            \OC\Files\Filesystem::signal_param_path => $hookPath
318
+        ));
319
+    }
320
+
321
+    /**
322
+     * Returns the data
323
+     *
324
+     * @return resource
325
+     * @throws Forbidden
326
+     * @throws ServiceUnavailable
327
+     */
328
+    public function get() {
329
+        //throw exception if encryption is disabled but files are still encrypted
330
+        try {
331
+            if (!$this->info->isReadable()) {
332
+                // do a if the file did not exist
333
+                throw new NotFound();
334
+            }
335
+            try {
336
+                $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
337
+            } catch (\Exception $e) {
338
+                $this->convertToSabreException($e);
339
+            }
340
+            if ($res === false) {
341
+                throw new ServiceUnavailable("Could not open file");
342
+            }
343
+            return $res;
344
+        } catch (GenericEncryptionException $e) {
345
+            // returning 503 will allow retry of the operation at a later point in time
346
+            throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
347
+        } catch (StorageNotAvailableException $e) {
348
+            throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
349
+        } catch (ForbiddenException $ex) {
350
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
351
+        } catch (LockedException $e) {
352
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
353
+        }
354
+    }
355
+
356
+    /**
357
+     * Delete the current file
358
+     *
359
+     * @throws Forbidden
360
+     * @throws ServiceUnavailable
361
+     */
362
+    public function delete() {
363
+        if (!$this->info->isDeletable()) {
364
+            throw new Forbidden();
365
+        }
366
+
367
+        try {
368
+            if (!$this->fileView->unlink($this->path)) {
369
+                // assume it wasn't possible to delete due to permissions
370
+                throw new Forbidden();
371
+            }
372
+        } catch (StorageNotAvailableException $e) {
373
+            throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
374
+        } catch (ForbiddenException $ex) {
375
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
376
+        } catch (LockedException $e) {
377
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
378
+        }
379
+    }
380
+
381
+    /**
382
+     * Returns the mime-type for a file
383
+     *
384
+     * If null is returned, we'll assume application/octet-stream
385
+     *
386
+     * @return string
387
+     */
388
+    public function getContentType() {
389
+        $mimeType = $this->info->getMimetype();
390
+
391
+        // PROPFIND needs to return the correct mime type, for consistency with the web UI
392
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
393
+            return $mimeType;
394
+        }
395
+        return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
396
+    }
397
+
398
+    /**
399
+     * @return array|false
400
+     */
401
+    public function getDirectDownload() {
402
+        if (\OCP\App::isEnabled('encryption')) {
403
+            return [];
404
+        }
405
+        /** @var \OCP\Files\Storage $storage */
406
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
407
+        if (is_null($storage)) {
408
+            return [];
409
+        }
410
+
411
+        return $storage->getDirectDownload($internalPath);
412
+    }
413
+
414
+    /**
415
+     * @param resource $data
416
+     * @return null|string
417
+     * @throws Exception
418
+     * @throws BadRequest
419
+     * @throws NotImplemented
420
+     * @throws ServiceUnavailable
421
+     */
422
+    private function createFileChunked($data) {
423
+        list($path, $name) = \Sabre\Uri\split($this->path);
424
+
425
+        $info = \OC_FileChunking::decodeName($name);
426
+        if (empty($info)) {
427
+            throw new NotImplemented('Invalid chunk name');
428
+        }
429
+
430
+        $chunk_handler = new \OC_FileChunking($info);
431
+        $bytesWritten = $chunk_handler->store($info['index'], $data);
432
+
433
+        //detect aborted upload
434
+        if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
435
+            if (isset($_SERVER['CONTENT_LENGTH'])) {
436
+                $expected = (int) $_SERVER['CONTENT_LENGTH'];
437
+                if ($bytesWritten !== $expected) {
438
+                    $chunk_handler->remove($info['index']);
439
+                    throw new BadRequest(
440
+                        'expected filesize ' . $expected . ' got ' . $bytesWritten);
441
+                }
442
+            }
443
+        }
444
+
445
+        if ($chunk_handler->isComplete()) {
446
+            list($storage,) = $this->fileView->resolvePath($path);
447
+            $needsPartFile = $this->needsPartFile($storage);
448
+            $partFile = null;
449
+
450
+            $targetPath = $path . '/' . $info['name'];
451
+            /** @var \OC\Files\Storage\Storage $targetStorage */
452
+            list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
453
+
454
+            $exists = $this->fileView->file_exists($targetPath);
455
+
456
+            try {
457
+                $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
458
+
459
+                $this->emitPreHooks($exists, $targetPath);
460
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
461
+                /** @var \OC\Files\Storage\Storage $targetStorage */
462
+                list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
463
+
464
+                if ($needsPartFile) {
465
+                    // we first assembly the target file as a part file
466
+                    $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
467
+                    /** @var \OC\Files\Storage\Storage $targetStorage */
468
+                    list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
469
+
470
+
471
+                    $chunk_handler->file_assemble($partStorage, $partInternalPath);
472
+
473
+                    // here is the final atomic rename
474
+                    $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
475
+                    $fileExists = $targetStorage->file_exists($targetInternalPath);
476
+                    if ($renameOkay === false || $fileExists === false) {
477
+                        \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::rename() failed', \OCP\Util::ERROR);
478
+                        // only delete if an error occurred and the target file was already created
479
+                        if ($fileExists) {
480
+                            // set to null to avoid double-deletion when handling exception
481
+                            // stray part file
482
+                            $partFile = null;
483
+                            $targetStorage->unlink($targetInternalPath);
484
+                        }
485
+                        $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
486
+                        throw new Exception('Could not rename part file assembled from chunks');
487
+                    }
488
+                } else {
489
+                    // assemble directly into the final file
490
+                    $chunk_handler->file_assemble($targetStorage, $targetInternalPath);
491
+                }
492
+
493
+                // allow sync clients to send the mtime along in a header
494
+                if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
495
+                    $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
496
+                    if ($targetStorage->touch($targetInternalPath, $mtime)) {
497
+                        $this->header('X-OC-MTime: accepted');
498
+                    }
499
+                }
500
+
501
+                // since we skipped the view we need to scan and emit the hooks ourselves
502
+                $targetStorage->getUpdater()->update($targetInternalPath);
503
+
504
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
505
+
506
+                $this->emitPostHooks($exists, $targetPath);
507
+
508
+                // FIXME: should call refreshInfo but can't because $this->path is not the of the final file
509
+                $info = $this->fileView->getFileInfo($targetPath);
510
+
511
+                if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
512
+                    $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
513
+                    $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
514
+                } else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
515
+                    $this->fileView->putFileInfo($this->path, ['checksum' => '']);
516
+                }
517
+
518
+                $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
519
+
520
+                return $info->getEtag();
521
+            } catch (\Exception $e) {
522
+                if ($partFile !== null) {
523
+                    $targetStorage->unlink($targetInternalPath);
524
+                }
525
+                $this->convertToSabreException($e);
526
+            }
527
+        }
528
+
529
+        return null;
530
+    }
531
+
532
+    /**
533
+     * Returns whether a part file is needed for the given storage
534
+     * or whether the file can be assembled/uploaded directly on the
535
+     * target storage.
536
+     *
537
+     * @param \OCP\Files\Storage $storage
538
+     * @return bool true if the storage needs part file handling
539
+     */
540
+    private function needsPartFile($storage) {
541
+        // TODO: in the future use ChunkHandler provided by storage
542
+        return !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') &&
543
+            !$storage->instanceOfStorage('OC\Files\Storage\OwnCloud') &&
544
+            $storage->needsPartFile();
545
+    }
546
+
547
+    /**
548
+     * Convert the given exception to a SabreException instance
549
+     *
550
+     * @param \Exception $e
551
+     *
552
+     * @throws \Sabre\DAV\Exception
553
+     */
554
+    private function convertToSabreException(\Exception $e) {
555
+        if ($e instanceof \Sabre\DAV\Exception) {
556
+            throw $e;
557
+        }
558
+        if ($e instanceof NotPermittedException) {
559
+            // a more general case - due to whatever reason the content could not be written
560
+            throw new Forbidden($e->getMessage(), 0, $e);
561
+        }
562
+        if ($e instanceof ForbiddenException) {
563
+            // the path for the file was forbidden
564
+            throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
565
+        }
566
+        if ($e instanceof EntityTooLargeException) {
567
+            // the file is too big to be stored
568
+            throw new EntityTooLarge($e->getMessage(), 0, $e);
569
+        }
570
+        if ($e instanceof InvalidContentException) {
571
+            // the file content is not permitted
572
+            throw new UnsupportedMediaType($e->getMessage(), 0, $e);
573
+        }
574
+        if ($e instanceof InvalidPathException) {
575
+            // the path for the file was not valid
576
+            // TODO: find proper http status code for this case
577
+            throw new Forbidden($e->getMessage(), 0, $e);
578
+        }
579
+        if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
580
+            // the file is currently being written to by another process
581
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
582
+        }
583
+        if ($e instanceof GenericEncryptionException) {
584
+            // returning 503 will allow retry of the operation at a later point in time
585
+            throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
586
+        }
587
+        if ($e instanceof StorageNotAvailableException) {
588
+            throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
589
+        }
590
+
591
+        throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
592
+    }
593
+
594
+    /**
595
+     * Get the checksum for this file
596
+     *
597
+     * @return string
598
+     */
599
+    public function getChecksum() {
600
+        return $this->info->getChecksum();
601
+    }
602
+
603
+    protected function header($string) {
604
+        \header($string);
605
+    }
606 606
 }
Please login to merge, or discard this patch.