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