Completed
Push — master ( 883574...c5dbe1 )
by Morris
13:12
created

QuotaPlugin::getFreeSpace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Felix Moeller <[email protected]>
6
 * @author Morris Jobke <[email protected]>
7
 * @author Robin Appelman <[email protected]>
8
 * @author scambra <[email protected]>
9
 * @author Thomas Müller <[email protected]>
10
 * @author Vincent Petry <[email protected]>
11
 *
12
 * @license AGPL-3.0
13
 *
14
 * This code is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License, version 3,
16
 * as published by the Free Software Foundation.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
 * GNU Affero General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Affero General Public License, version 3,
24
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
25
 *
26
 */
27
namespace OCA\DAV\Connector\Sabre;
28
use OCA\DAV\Files\FilesHome;
29
use OCA\DAV\Upload\FutureFile;
30
use OCA\DAV\Upload\UploadFolder;
31
use OCP\Files\FileInfo;
32
use OCP\Files\StorageNotAvailableException;
33
use Sabre\DAV\Exception\InsufficientStorage;
34
use Sabre\DAV\Exception\ServiceUnavailable;
35
use Sabre\DAV\INode;
36
use Sabre\HTTP\URLUtil;
37
38
/**
39
 * This plugin check user quota and deny creating files when they exceeds the quota.
40
 *
41
 * @author Sergio Cambra
42
 * @copyright Copyright (C) 2012 entreCables S.L. All rights reserved.
43
 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
44
 */
45
class QuotaPlugin extends \Sabre\DAV\ServerPlugin {
46
47
	/** @var \OC\Files\View */
48
	private $view;
49
50
	/**
51
	 * Reference to main server object
52
	 *
53
	 * @var \Sabre\DAV\Server
54
	 */
55
	private $server;
56
57
	/**
58
	 * @param \OC\Files\View $view
59
	 */
60
	public function __construct($view) {
61
		$this->view = $view;
62
	}
63
64
	/**
65
	 * This initializes the plugin.
66
	 *
67
	 * This function is called by \Sabre\DAV\Server, after
68
	 * addPlugin is called.
69
	 *
70
	 * This method should set up the requires event subscriptions.
71
	 *
72
	 * @param \Sabre\DAV\Server $server
73
	 * @return void
74
	 */
75
	public function initialize(\Sabre\DAV\Server $server) {
76
77
		$this->server = $server;
78
79
		$server->on('beforeWriteContent', [$this, 'beforeWriteContent'], 10);
80
		$server->on('beforeCreateFile', [$this, 'beforeCreateFile'], 10);
81
		$server->on('beforeMove', [$this, 'beforeMove'], 10);
82
	}
83
84
	/**
85
	 * Check quota before creating file
86
	 *
87
	 * @param string $uri target file URI
88
	 * @param resource $data data
89
	 * @param INode $parent Sabre Node
90
	 * @param bool $modified modified
91
	 */
92
	public function beforeCreateFile($uri, $data, INode $parent, $modified) {
93
		if (!$parent instanceof Node) {
94
			return;
95
		}
96
97
		return $this->checkQuota($parent->getPath() . '/' . basename($uri));
98
	}
99
100
	/**
101
	 * Check quota before writing content
102
	 *
103
	 * @param string $uri target file URI
104
	 * @param INode $node Sabre Node
105
	 * @param resource $data data
106
	 * @param bool $modified modified
107
	 */
108
	public function beforeWriteContent($uri, INode $node, $data, $modified) {
109
		if (!$node instanceof Node) {
110
			return;
111
		}
112
113
		return $this->checkQuota($node->getPath());
114
	}
115
116
	/**
117
	 * Check if we're moving a Futurefile in which case we need to check
118
	 * the quota on the target destination.
119
	 *
120
	 * @param string $source source path
121
	 * @param string $destination destination path
122
	 */
123
	public function beforeMove($source, $destination) {
124
		$sourceNode = $this->server->tree->getNodeForPath($source);
125
		if (!$sourceNode instanceof FutureFile) {
126
			return;
127
		}
128
129
		// get target node for proper path conversion
130
		if ($this->server->tree->nodeExists($destination)) {
131
			$destinationNode = $this->server->tree->getNodeForPath($destination);
132
			$path = $destinationNode->getPath();
133
		} else {
134
			$parentNode = $this->server->tree->getNodeForPath(dirname($destination));
135
			$path = $parentNode->getPath();
136
		}
137
138
		return $this->checkQuota($path, $sourceNode->getSize());
139
	}
140
141
142
	/**
143
	 * This method is called before any HTTP method and validates there is enough free space to store the file
144
	 *
145
	 * @param string $path relative to the users home
146
	 * @param int $length
147
	 * @throws InsufficientStorage
148
	 * @return bool
149
	 */
150
	public function checkQuota($path, $length = null) {
151
		if ($length === null) {
152
			$length = $this->getLength();
153
		}
154
155
		if ($length) {
156
			list($parentPath, $newName) = \Sabre\Uri\split($path);
157
			if(is_null($parentPath)) {
158
				$parentPath = '';
159
			}
160
			$req = $this->server->httpRequest;
161
			if ($req->getHeader('OC-Chunked')) {
162
				$info = \OC_FileChunking::decodeName($newName);
163
				$chunkHandler = $this->getFileChunking($info);
164
				// subtract the already uploaded size to see whether
165
				// there is still enough space for the remaining chunks
166
				$length -= $chunkHandler->getCurrentSize();
167
				// use target file name for free space check in case of shared files
168
				$path = rtrim($parentPath, '/') . '/' . $info['name'];
169
			}
170
			$freeSpace = $this->getFreeSpace($path);
171
			if ($freeSpace !== FileInfo::SPACE_UNKNOWN && $freeSpace !== FileInfo::SPACE_UNLIMITED && $length > $freeSpace) {
172
				if (isset($chunkHandler)) {
173
					$chunkHandler->cleanup();
174
				}
175
				throw new InsufficientStorage();
176
			}
177
		}
178
		return true;
179
	}
180
181
	public function getFileChunking($info) {
182
		// FIXME: need a factory for better mocking support
183
		return new \OC_FileChunking($info);
184
	}
185
186
	public function getLength() {
187
		$req = $this->server->httpRequest;
188
		$length = $req->getHeader('X-Expected-Entity-Length');
189
		if (!is_numeric($length)) {
190
			$length = $req->getHeader('Content-Length');
191
			$length = is_numeric($length) ? $length : null;
192
		}
193
194
		$ocLength = $req->getHeader('OC-Total-Length');
195
		if (is_numeric($length) && is_numeric($ocLength)) {
196
			return max($length, $ocLength);
197
		}
198
199
		return $length;
200
	}
201
202
	/**
203
	 * @param string $uri
204
	 * @return mixed
205
	 * @throws ServiceUnavailable
206
	 */
207
	public function getFreeSpace($uri) {
208
		try {
209
			$freeSpace = $this->view->free_space(ltrim($uri, '/'));
210
			return $freeSpace;
211
		} catch (StorageNotAvailableException $e) {
212
			throw new ServiceUnavailable($e->getMessage());
213
		}
214
	}
215
}
216