Blame view

sources/lib/private/connector/sabre/file.php 8.59 KB
03e52840d   Kload   Init
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  <?php
  
  /**
   * ownCloud
   *
   * @author Jakob Sack
   * @copyright 2011 Jakob Sack kde@jakobsack.de
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
   * License as published by the Free Software Foundation; either
   * version 3 of the License, or any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
   *
   * You should have received a copy of the GNU Affero General Public
   * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
   *
   */
6d9380f96   Cédric Dupont   Update sources OC...
23
  class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements \Sabre\DAV\IFile {
03e52840d   Kload   Init
24
25
26
27
28
29
  
  	/**
  	 * Updates the data
  	 *
  	 * The data argument is a readable stream resource.
  	 *
31b7f2792   Kload   Upgrade to ownclo...
30
  	 * After a successful put operation, you may choose to return an ETag. The
03e52840d   Kload   Init
31
32
33
34
35
36
37
38
39
40
41
42
  	 * etag must always be surrounded by double-quotes. These quotes must
  	 * appear in the actual string you're returning.
  	 *
  	 * Clients may use the ETag from a PUT request to later on make sure that
  	 * when they update the file, the contents haven't changed in the mean
  	 * time.
  	 *
  	 * If you don't plan to store the file byte-by-byte, and you return a
  	 * different object on a subsequent GET you are strongly recommended to not
  	 * return an ETag, and just return null.
  	 *
  	 * @param resource $data
6d9380f96   Cédric Dupont   Update sources OC...
43
44
45
46
47
48
  	 * @throws \Sabre\DAV\Exception\Forbidden
  	 * @throws OC_Connector_Sabre_Exception_UnsupportedMediaType
  	 * @throws \Sabre\DAV\Exception\BadRequest
  	 * @throws \Sabre\DAV\Exception
  	 * @throws OC_Connector_Sabre_Exception_EntityTooLarge
  	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
03e52840d   Kload   Init
49
50
51
  	 * @return string|null
  	 */
  	public function put($data) {
6d9380f96   Cédric Dupont   Update sources OC...
52
53
54
  		if ($this->info && $this->fileView->file_exists($this->path) &&
  			!$this->info->isUpdateable()) {
  			throw new \Sabre\DAV\Exception\Forbidden();
03e52840d   Kload   Init
55
  		}
31b7f2792   Kload   Upgrade to ownclo...
56
57
  		// throw an exception if encryption was disabled but the files are still encrypted
  		if (\OC_Util::encryptedFiles()) {
6d9380f96   Cédric Dupont   Update sources OC...
58
59
60
61
62
63
  			throw new \Sabre\DAV\Exception\ServiceUnavailable();
  		}
  
  		$fileName = basename($this->path);
  		if (!\OCP\Util::isValidFileName($fileName)) {
  			throw new \Sabre\DAV\Exception\BadRequest();
31b7f2792   Kload   Upgrade to ownclo...
64
65
66
67
68
69
  		}
  
  		// chunked handling
  		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
  			return $this->createFileChunked($data);
  		}
03e52840d   Kload   Init
70
  		// mark file as partial while uploading (ignored by the scanner)
6d9380f96   Cédric Dupont   Update sources OC...
71
  		$partFilePath = $this->path . '.ocTransferId' . rand() . '.part';
03e52840d   Kload   Init
72

31b7f2792   Kload   Upgrade to ownclo...
73
  		try {
6d9380f96   Cédric Dupont   Update sources OC...
74
  			$putOkay = $this->fileView->file_put_contents($partFilePath, $data);
31b7f2792   Kload   Upgrade to ownclo...
75
76
  			if ($putOkay === false) {
  				\OC_Log::write('webdav', '\OC\Files\Filesystem::file_put_contents() failed', \OC_Log::ERROR);
6d9380f96   Cédric Dupont   Update sources OC...
77
  				$this->fileView->unlink($partFilePath);
31b7f2792   Kload   Upgrade to ownclo...
78
  				// because we have no clue about the cause we can only throw back a 500/Internal Server Error
6d9380f96   Cédric Dupont   Update sources OC...
79
  				throw new \Sabre\DAV\Exception('Could not write file contents');
03e52840d   Kload   Init
80
  			}
31b7f2792   Kload   Upgrade to ownclo...
81
82
  		} catch (\OCP\Files\NotPermittedException $e) {
  			// a more general case - due to whatever reason the content could not be written
6d9380f96   Cédric Dupont   Update sources OC...
83
  			throw new \Sabre\DAV\Exception\Forbidden($e->getMessage());
31b7f2792   Kload   Upgrade to ownclo...
84
85
86
87
88
89
90
91
92
93
94
95
  
  		} catch (\OCP\Files\EntityTooLargeException $e) {
  			// the file is too big to be stored
  			throw new OC_Connector_Sabre_Exception_EntityTooLarge($e->getMessage());
  
  		} catch (\OCP\Files\InvalidContentException $e) {
  			// the file content is not permitted
  			throw new OC_Connector_Sabre_Exception_UnsupportedMediaType($e->getMessage());
  
  		} catch (\OCP\Files\InvalidPathException $e) {
  			// the path for the file was not valid
  			// TODO: find proper http status code for this case
6d9380f96   Cédric Dupont   Update sources OC...
96
97
98
99
100
101
102
103
104
105
106
107
108
  			throw new \Sabre\DAV\Exception\Forbidden($e->getMessage());
  		} catch (\OCP\Files\LockNotAcquiredException $e) {
  			// the file is currently being written to by another process
  			throw new OC_Connector_Sabre_Exception_FileLocked($e->getMessage(), $e->getCode(), $e);
  		}
  
  		// double check if the file was fully received
  		// compare expected and actual size
  		$expected = $_SERVER['CONTENT_LENGTH'];
  		$actual = $this->fileView->filesize($partFilePath);
  		if ($actual != $expected) {
  			$this->fileView->unlink($partFilePath);
  			throw new \Sabre\DAV\Exception\BadRequest('expected filesize ' . $expected . ' got ' . $actual);
03e52840d   Kload   Init
109
110
111
  		}
  
  		// rename to correct path
6d9380f96   Cédric Dupont   Update sources OC...
112
113
114
115
116
117
118
119
120
121
122
123
  		try {
  			$renameOkay = $this->fileView->rename($partFilePath, $this->path);
  			$fileExists = $this->fileView->file_exists($this->path);
  			if ($renameOkay === false || $fileExists === false) {
  				\OC_Log::write('webdav', '\OC\Files\Filesystem::rename() failed', \OC_Log::ERROR);
  				$this->fileView->unlink($partFilePath);
  				throw new \Sabre\DAV\Exception('Could not rename part file to final file');
  			}
  		}
  		catch (\OCP\Files\LockNotAcquiredException $e) {
  			// the file is currently being written to by another process
  			throw new OC_Connector_Sabre_Exception_FileLocked($e->getMessage(), $e->getCode(), $e);
03e52840d   Kload   Init
124
  		}
31b7f2792   Kload   Upgrade to ownclo...
125
  		// allow sync clients to send the mtime along in a header
03e52840d   Kload   Init
126
127
  		$mtime = OC_Request::hasModificationTime();
  		if ($mtime !== false) {
6d9380f96   Cédric Dupont   Update sources OC...
128
  			if($this->fileView->touch($this->path, $mtime)) {
03e52840d   Kload   Init
129
130
131
  				header('X-OC-MTime: accepted');
  			}
  		}
6d9380f96   Cédric Dupont   Update sources OC...
132
  		$this->refreshInfo();
03e52840d   Kload   Init
133

6d9380f96   Cédric Dupont   Update sources OC...
134
  		return '"' . $this->info->getEtag() . '"';
03e52840d   Kload   Init
135
136
137
138
139
  	}
  
  	/**
  	 * Returns the data
  	 *
6d9380f96   Cédric Dupont   Update sources OC...
140
  	 * @return string|resource
03e52840d   Kload   Init
141
142
  	 */
  	public function get() {
31b7f2792   Kload   Upgrade to ownclo...
143
144
  		//throw exception if encryption is disabled but files are still encrypted
  		if (\OC_Util::encryptedFiles()) {
6d9380f96   Cédric Dupont   Update sources OC...
145
  			throw new \Sabre\DAV\Exception\ServiceUnavailable();
31b7f2792   Kload   Upgrade to ownclo...
146
  		} else {
6d9380f96   Cédric Dupont   Update sources OC...
147
  			return $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
31b7f2792   Kload   Upgrade to ownclo...
148
  		}
03e52840d   Kload   Init
149
150
151
152
153
154
155
  
  	}
  
  	/**
  	 * Delete the current file
  	 *
  	 * @return void
6d9380f96   Cédric Dupont   Update sources OC...
156
  	 * @throws \Sabre\DAV\Exception\Forbidden
03e52840d   Kload   Init
157
158
  	 */
  	public function delete() {
6d9380f96   Cédric Dupont   Update sources OC...
159
160
  		if (!$this->info->isDeletable()) {
  			throw new \Sabre\DAV\Exception\Forbidden();
03e52840d   Kload   Init
161
  		}
6d9380f96   Cédric Dupont   Update sources OC...
162
  		$this->fileView->unlink($this->path);
03e52840d   Kload   Init
163

31b7f2792   Kload   Upgrade to ownclo...
164
165
  		// remove properties
  		$this->removeProperties();
03e52840d   Kload   Init
166
167
168
169
170
  	}
  
  	/**
  	 * Returns the size of the node, in bytes
  	 *
6d9380f96   Cédric Dupont   Update sources OC...
171
  	 * @return int|float
03e52840d   Kload   Init
172
173
  	 */
  	public function getSize() {
6d9380f96   Cédric Dupont   Update sources OC...
174
  		return $this->info->getSize();
03e52840d   Kload   Init
175
176
177
178
179
180
181
  	}
  
  	/**
  	 * Returns the ETag for a file
  	 *
  	 * An ETag is a unique identifier representing the current version of the
  	 * file. If the file changes, the ETag MUST change.  The ETag is an
31b7f2792   Kload   Upgrade to ownclo...
182
  	 * arbitrary string, but MUST be surrounded by double-quotes.
03e52840d   Kload   Init
183
184
185
186
187
188
  	 *
  	 * Return null if the ETag can not effectively be determined
  	 *
  	 * @return mixed
  	 */
  	public function getETag() {
6d9380f96   Cédric Dupont   Update sources OC...
189
  		return '"' . $this->info->getEtag() . '"';
03e52840d   Kload   Init
190
191
192
193
194
195
196
197
198
199
  	}
  
  	/**
  	 * Returns the mime-type for a file
  	 *
  	 * If null is returned, we'll assume application/octet-stream
  	 *
  	 * @return mixed
  	 */
  	public function getContentType() {
6d9380f96   Cédric Dupont   Update sources OC...
200
  		$mimeType = $this->info->getMimetype();
03e52840d   Kload   Init
201

837968727   Kload   [enh] Upgrade to ...
202
  		return \OC_Helper::getSecureMimeType($mimeType);
03e52840d   Kload   Init
203
  	}
31b7f2792   Kload   Upgrade to ownclo...
204

6d9380f96   Cédric Dupont   Update sources OC...
205
206
207
208
  	/**
  	 * @param resource $data
  	 * @return null|string
  	 */
31b7f2792   Kload   Upgrade to ownclo...
209
210
  	private function createFileChunked($data)
  	{
6d9380f96   Cédric Dupont   Update sources OC...
211
  		list($path, $name) = \Sabre\DAV\URLUtil::splitPath($this->path);
31b7f2792   Kload   Upgrade to ownclo...
212
213
214
  
  		$info = OC_FileChunking::decodeName($name);
  		if (empty($info)) {
6d9380f96   Cédric Dupont   Update sources OC...
215
  			throw new \Sabre\DAV\Exception\NotImplemented();
31b7f2792   Kload   Upgrade to ownclo...
216
217
218
219
220
221
222
223
224
225
  		}
  		$chunk_handler = new OC_FileChunking($info);
  		$bytesWritten = $chunk_handler->store($info['index'], $data);
  
  		//detect aborted upload
  		if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT' ) {
  			if (isset($_SERVER['CONTENT_LENGTH'])) {
  				$expected = $_SERVER['CONTENT_LENGTH'];
  				if ($bytesWritten != $expected) {
  					$chunk_handler->remove($info['index']);
6d9380f96   Cédric Dupont   Update sources OC...
226
  					throw new \Sabre\DAV\Exception\BadRequest(
31b7f2792   Kload   Upgrade to ownclo...
227
228
229
230
231
232
233
234
235
236
237
238
  						'expected filesize ' . $expected . ' got ' . $bytesWritten);
  				}
  			}
  		}
  
  		if ($chunk_handler->isComplete()) {
  
  			// we first assembly the target file as a part file
  			$partFile = $path . '/' . $info['name'] . '.ocTransferId' . $info['transferid'] . '.part';
  			$chunk_handler->file_assemble($partFile);
  
  			// here is the final atomic rename
31b7f2792   Kload   Upgrade to ownclo...
239
  			$targetPath = $path . '/' . $info['name'];
6d9380f96   Cédric Dupont   Update sources OC...
240
241
  			$renameOkay = $this->fileView->rename($partFile, $targetPath);
  			$fileExists = $this->fileView->file_exists($targetPath);
31b7f2792   Kload   Upgrade to ownclo...
242
243
  			if ($renameOkay === false || $fileExists === false) {
  				\OC_Log::write('webdav', '\OC\Files\Filesystem::rename() failed', \OC_Log::ERROR);
a293d369c   Kload   Update sources to...
244
245
  				// only delete if an error occurred and the target file was already created
  				if ($fileExists) {
6d9380f96   Cédric Dupont   Update sources OC...
246
  					$this->fileView->unlink($targetPath);
a293d369c   Kload   Update sources to...
247
  				}
6d9380f96   Cédric Dupont   Update sources OC...
248
  				throw new \Sabre\DAV\Exception('Could not rename part file assembled from chunks');
31b7f2792   Kload   Upgrade to ownclo...
249
250
251
252
253
  			}
  
  			// allow sync clients to send the mtime along in a header
  			$mtime = OC_Request::hasModificationTime();
  			if ($mtime !== false) {
6d9380f96   Cédric Dupont   Update sources OC...
254
  				if($this->fileView->touch($targetPath, $mtime)) {
31b7f2792   Kload   Upgrade to ownclo...
255
256
257
  					header('X-OC-MTime: accepted');
  				}
  			}
6d9380f96   Cédric Dupont   Update sources OC...
258
259
  			$info = $this->fileView->getFileInfo($targetPath);
  			return $info->getEtag();
31b7f2792   Kload   Upgrade to ownclo...
260
261
262
263
  		}
  
  		return null;
  	}
03e52840d   Kload   Init
264
  }