Merge pull request #636 from dawnbreak/RedDAV
Moved classes from reddav.php into own files.
This commit is contained in:
		
							
								
								
									
										208
									
								
								include/RedDAV/RedBasicAuth.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								include/RedDAV/RedBasicAuth.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,208 @@ | ||||
| <?php | ||||
|  | ||||
| namespace RedMatrix\RedDAV; | ||||
|  | ||||
| use Sabre\DAV; | ||||
|  | ||||
| /** | ||||
|  * @brief Authentication backend class for RedDAV. | ||||
|  * | ||||
|  * This class also contains some data which is not necessary for authentication | ||||
|  * like timezone settings. | ||||
|  * | ||||
|  * @extends Sabre\DAV\Auth\Backend\AbstractBasic | ||||
|  * | ||||
|  * @link http://github.com/friendica/red | ||||
|  * @license http://opensource.org/licenses/mit-license.php The MIT License (MIT) | ||||
|  */ | ||||
| class RedBasicAuth extends DAV\Auth\Backend\AbstractBasic { | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief This variable holds the currently logged-in channel_address. | ||||
| 	 * | ||||
| 	 * It is used for building path in filestorage/. | ||||
| 	 * | ||||
| 	 * @var string|null | ||||
| 	 */ | ||||
| 	protected $channel_name = null; | ||||
| 	/** | ||||
| 	 * channel_id of the current channel of the logged-in account. | ||||
| 	 * | ||||
| 	 * @var int | ||||
| 	 */ | ||||
| 	public $channel_id = 0; | ||||
| 	/** | ||||
| 	 * channel_hash of the current channel of the logged-in account. | ||||
| 	 * | ||||
| 	 * @var string | ||||
| 	 */ | ||||
| 	public $channel_hash = ''; | ||||
| 	/** | ||||
| 	 * Set in mod/cloud.php to observer_hash. | ||||
| 	 * | ||||
| 	 * @var string | ||||
| 	 */ | ||||
| 	public $observer = ''; | ||||
| 	/** | ||||
| 	 * | ||||
| 	 * @see RedBrowser::set_writeable() | ||||
| 	 * @var DAV\Browser\Plugin | ||||
| 	 */ | ||||
| 	public $browser; | ||||
| 	/** | ||||
| 	 * channel_id of the current visited path. Set in RedDirectory::getDir(). | ||||
| 	 * | ||||
| 	 * @var int | ||||
| 	 */ | ||||
| 	public $owner_id = 0; | ||||
| 	/** | ||||
| 	 * channel_name of the current visited path. Set in RedDirectory::getDir(). | ||||
| 	 * | ||||
| 	 * Used for creating the path in cloud/ | ||||
| 	 * | ||||
| 	 * @var string | ||||
| 	 */ | ||||
| 	public $owner_nick = ''; | ||||
| 	/** | ||||
| 	 * Timezone from the visiting channel's channel_timezone. | ||||
| 	 * | ||||
| 	 * Used in @ref RedBrowser | ||||
| 	 * | ||||
| 	 * @var string | ||||
| 	 */ | ||||
| 	protected $timezone = ''; | ||||
|  | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Validates a username and password. | ||||
| 	 * | ||||
| 	 * Guest access is granted with the password "+++". | ||||
| 	 * | ||||
| 	 * @see \Sabre\DAV\Auth\Backend\AbstractBasic::validateUserPass | ||||
| 	 * @param string $username | ||||
| 	 * @param string $password | ||||
| 	 * @return bool | ||||
| 	 */ | ||||
| 	protected function validateUserPass($username, $password) { | ||||
| 		if (trim($password) === '+++') { | ||||
| 			logger('(DAV): RedBasicAuth::validateUserPass(): guest ' . $username); | ||||
| 			return true; | ||||
| 		} | ||||
|  | ||||
| 		require_once('include/auth.php'); | ||||
| 		$record = account_verify_password($username, $password); | ||||
| 		if ($record && $record['account_default_channel']) { | ||||
| 			$r = q("SELECT * FROM channel WHERE channel_account_id = %d AND channel_id = %d LIMIT 1", | ||||
| 				intval($record['account_id']), | ||||
| 				intval($record['account_default_channel']) | ||||
| 			); | ||||
| 			if ($r) { | ||||
| 				return $this->setAuthenticated($r[0]); | ||||
| 			} | ||||
| 		} | ||||
| 		$r = q("SELECT * FROM channel WHERE channel_address = '%s' LIMIT 1", | ||||
| 			dbesc($username) | ||||
| 		); | ||||
| 		if ($r) { | ||||
| 			$x = q("SELECT account_flags, account_salt, account_password FROM account WHERE account_id = %d LIMIT 1", | ||||
| 				intval($r[0]['channel_account_id']) | ||||
| 			); | ||||
| 			if ($x) { | ||||
| 				// @fixme this foreach should not be needed? | ||||
| 				foreach ($x as $record) { | ||||
| 					if (($record['account_flags'] == ACCOUNT_OK) || ($record['account_flags'] == ACCOUNT_UNVERIFIED) | ||||
| 					&& (hash('whirlpool', $record['account_salt'] . $password) === $record['account_password'])) { | ||||
| 						logger('(DAV) RedBasicAuth: password verified for ' . $username); | ||||
| 						return $this->setAuthenticated($r[0]); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		logger('(DAV) RedBasicAuth: password failed for ' . $username); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Sets variables and session parameters after successfull authentication. | ||||
| 	 * | ||||
| 	 * @param array $r | ||||
| 	 *  Array with the values for the authenticated channel. | ||||
| 	 * @return bool | ||||
| 	 */ | ||||
| 	protected function setAuthenticated($r) { | ||||
| 		$this->channel_name = $r['channel_address']; | ||||
| 		$this->channel_id = $r['channel_id']; | ||||
| 		$this->channel_hash = $this->observer = $r['channel_hash']; | ||||
| 		$_SESSION['uid'] = $r['channel_id']; | ||||
| 		$_SESSION['account_id'] = $r['channel_account_id']; | ||||
| 		$_SESSION['authenticated'] = true; | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Sets the channel_name from the currently logged-in channel. | ||||
| 	 * | ||||
| 	 * @param string $name | ||||
| 	 *  The channel's name | ||||
| 	 */ | ||||
| 	public function setCurrentUser($name) { | ||||
| 		$this->channel_name = $name; | ||||
| 	} | ||||
| 	/** | ||||
| 	 * Returns information about the currently logged-in channel. | ||||
| 	 * | ||||
| 	 * If nobody is currently logged in, this method should return null. | ||||
| 	 * | ||||
| 	 * @see \Sabre\DAV\Auth\Backend\AbstractBasic::getCurrentUser | ||||
| 	 * @return string|null | ||||
| 	 */ | ||||
| 	public function getCurrentUser() { | ||||
| 		return $this->channel_name; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Sets the timezone from the channel in RedBasicAuth. | ||||
| 	 * | ||||
| 	 * Set in mod/cloud.php if the channel has a timezone set. | ||||
| 	 * | ||||
| 	 * @param string $timezone | ||||
| 	 *  The channel's timezone. | ||||
| 	 * @return void | ||||
| 	 */ | ||||
| 	public function setTimezone($timezone) { | ||||
| 		$this->timezone = $timezone; | ||||
| 	} | ||||
| 	/** | ||||
| 	 * @brief Returns the timezone. | ||||
| 	 * | ||||
| 	 * @return string | ||||
| 	 *  Return the channel's timezone. | ||||
| 	 */ | ||||
| 	public function getTimezone() { | ||||
| 		return $this->timezone; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Set browser plugin for SabreDAV. | ||||
| 	 * | ||||
| 	 * @see RedBrowser::set_writeable() | ||||
| 	 * @param DAV\Browser\Plugin $browser | ||||
| 	 */ | ||||
| 	public function setBrowserPlugin($browser) { | ||||
| 		$this->browser = $browser; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Prints out all RedBasicAuth variables to logger(). | ||||
| 	 * | ||||
| 	 * @return void | ||||
| 	 */ | ||||
| 	public function log() { | ||||
| 		logger('dav: auth: channel_name ' . $this->channel_name, LOGGER_DATA); | ||||
| 		logger('dav: auth: channel_id ' . $this->channel_id, LOGGER_DATA); | ||||
| 		logger('dav: auth: channel_hash ' . $this->channel_hash, LOGGER_DATA); | ||||
| 		logger('dav: auth: observer ' . $this->observer, LOGGER_DATA); | ||||
| 		logger('dav: auth: owner_id ' . $this->owner_id, LOGGER_DATA); | ||||
| 		logger('dav: auth: owner_nick ' . $this->owner_nick, LOGGER_DATA); | ||||
| 	} | ||||
| } | ||||
| @@ -1,10 +1,4 @@ | ||||
| <?php | ||||
| /** | ||||
|  * RedMatrix - "The Network" | ||||
|  * | ||||
|  * @link http://github.com/friendica/red | ||||
|  * @license http://opensource.org/licenses/mit-license.php The MIT License (MIT) | ||||
|  */ | ||||
|  | ||||
| namespace RedMatrix\RedDAV; | ||||
|  | ||||
| @@ -17,6 +11,9 @@ use Sabre\DAV; | ||||
|  * for the webbrowser. | ||||
|  * | ||||
|  * @extends \Sabre\DAV\Browser\Plugin | ||||
|  * | ||||
|  * @link http://github.com/friendica/red | ||||
|  * @license http://opensource.org/licenses/mit-license.php The MIT License (MIT) | ||||
|  */ | ||||
| class RedBrowser extends DAV\Browser\Plugin { | ||||
|  | ||||
| @@ -33,6 +30,8 @@ class RedBrowser extends DAV\Browser\Plugin { | ||||
| 	 * $enablePost will be activated through set_writeable() in a later stage. | ||||
| 	 * At the moment the write_storage permission is only valid for the whole | ||||
| 	 * folder. No file specific permissions yet. | ||||
| 	 * @todo disable enablePost by default and only activate if permissions | ||||
| 	 * grant edit rights. | ||||
| 	 * | ||||
| 	 * Disable assets with $enableAssets = false. Should get some thumbnail views | ||||
| 	 * anyway. | ||||
| @@ -52,7 +51,11 @@ class RedBrowser extends DAV\Browser\Plugin { | ||||
| 	 * call the following function to decide whether or not to show web elements | ||||
| 	 * which include writeable objects. | ||||
| 	 * | ||||
| 	 * @todo Maybe this can be solved with some $server->subscribeEvent()? | ||||
| 	 * @fixme It only disable/enable the visible parts. Not the POST handler | ||||
| 	 * which handels the actual requests when uploading files or creating folders. | ||||
| 	 * | ||||
| 	 * @todo Maybe this whole way of doing this can be solved with some | ||||
| 	 * $server->subscribeEvent(). | ||||
| 	 */ | ||||
| 	public function set_writeable() { | ||||
| 		if (! $this->auth->owner_id) { | ||||
|   | ||||
							
								
								
									
										461
									
								
								include/RedDAV/RedDirectory.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										461
									
								
								include/RedDAV/RedDirectory.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,461 @@ | ||||
| <?php | ||||
|  | ||||
| namespace RedMatrix\RedDAV; | ||||
|  | ||||
| use Sabre\DAV; | ||||
|  | ||||
| /** | ||||
|  * @brief RedDirectory class. | ||||
|  * | ||||
|  * A class that represents a directory. | ||||
|  * | ||||
|  * @extends \Sabre\DAV\Node | ||||
|  * @implements \Sabre\DAV\ICollection | ||||
|  * @implements \Sabre\DAV\IQuota | ||||
|  * | ||||
|  * @link http://github.com/friendica/red | ||||
|  * @license http://opensource.org/licenses/mit-license.php The MIT License (MIT) | ||||
|  */ | ||||
| class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief The path inside /cloud | ||||
| 	 * | ||||
| 	 * @var string | ||||
| 	 */ | ||||
| 	private $red_path; | ||||
| 	private $folder_hash; | ||||
| 	/** | ||||
| 	 * @brief The full path as seen in the browser. | ||||
| 	 * /cloud + $red_path | ||||
| 	 * @todo I think this is not used anywhere, we always strip '/cloud' and only use it in debug | ||||
| 	 * @var string | ||||
| 	 */ | ||||
| 	private $ext_path; | ||||
| 	private $root_dir = ''; | ||||
| 	private $auth; | ||||
| 	/** | ||||
| 	 * @brief The real path on the filesystem. | ||||
| 	 * The actual path in store/ with the hashed names. | ||||
| 	 * | ||||
| 	 * @var string | ||||
| 	 */ | ||||
| 	private $os_path = ''; | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Sets up the directory node, expects a full path. | ||||
| 	 * | ||||
| 	 * @param string $ext_path a full path | ||||
| 	 * @param RedBasicAuth &$auth_plugin | ||||
| 	 */ | ||||
| 	public function __construct($ext_path, &$auth_plugin) { | ||||
| 		logger('RedDirectory::__construct() ' . $ext_path, LOGGER_DATA); | ||||
| 		$this->ext_path = $ext_path; | ||||
| 		// remove "/cloud" from the beginning of the path | ||||
| 		$this->red_path = ((strpos($ext_path, '/cloud') === 0) ? substr($ext_path, 6) : $ext_path); | ||||
| 		if (! $this->red_path) { | ||||
| 			$this->red_path = '/'; | ||||
| 		} | ||||
| 		$this->auth = $auth_plugin; | ||||
| 		$this->folder_hash = ''; | ||||
| 		$this->getDir(); | ||||
|  | ||||
| 		if ($this->auth->browser) { | ||||
| 			$this->auth->browser->set_writeable(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private function log() { | ||||
| 		logger('RedDirectory::log() ext_path ' . $this->ext_path, LOGGER_DATA); | ||||
| 		logger('RedDirectory::log() os_path ' . $this->os_path, LOGGER_DATA); | ||||
| 		logger('RedDirectory::log() red_path ' . $this->red_path, LOGGER_DATA); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Returns an array with all the child nodes. | ||||
| 	 * | ||||
| 	 * @throws DAV\Exception\Forbidden | ||||
| 	 * @return array DAV\INode[] | ||||
| 	 */ | ||||
| 	public function getChildren() { | ||||
| 		logger('RedDirectory::getChildren() called for ' . $this->ext_path, LOGGER_DATA); | ||||
| 		$this->log(); | ||||
|  | ||||
| 		if (get_config('system', 'block_public') && (! $this->auth->channel_id) && (! $this->auth->observer)) { | ||||
| 			throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 		} | ||||
|  | ||||
| 		if (($this->auth->owner_id) && (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'view_storage'))) { | ||||
| 			throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 		} | ||||
|  | ||||
| 		$contents = RedCollectionData($this->red_path, $this->auth); | ||||
| 		return $contents; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Returns a child by name. | ||||
| 	 * | ||||
| 	 * | ||||
| 	 * @throw DAV\Exception\Forbidden | ||||
| 	 * @throw DAV\Exception\NotFound | ||||
| 	 * @param string $name | ||||
| 	 */ | ||||
| 	public function getChild($name) { | ||||
| 		logger('RedDirectory::getChild(): ' . $name, LOGGER_DATA); | ||||
|  | ||||
| 		if (get_config('system', 'block_public') && (! $this->auth->channel_id) && (! $this->auth->observer)) { | ||||
| 			throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 		} | ||||
|  | ||||
| 		if (($this->auth->owner_id) && (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'view_storage'))) { | ||||
| 			throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 		} | ||||
|  | ||||
| 		if ($this->red_path === '/' && $name === 'cloud') { | ||||
| 			return new RedDirectory('/cloud', $this->auth); | ||||
| 		} | ||||
|  | ||||
| 		$x = RedFileData($this->ext_path . '/' . $name, $this->auth); | ||||
| 		if ($x) { | ||||
| 			return $x; | ||||
| 		} | ||||
|  | ||||
| 		throw new DAV\Exception\NotFound('The file with name: ' . $name . ' could not be found.'); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Returns the name of the directory. | ||||
| 	 * | ||||
| 	 * @return string | ||||
| 	 */ | ||||
| 	public function getName() { | ||||
| 		logger('RedDirectory::getName() returns: ' . basename($this->red_path), LOGGER_DATA); | ||||
| 		return (basename($this->red_path)); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Renames the directory. | ||||
| 	 * | ||||
| 	 * @todo handle duplicate directory name | ||||
| 	 * | ||||
| 	 * @throw DAV\Exception\Forbidden | ||||
| 	 * @param string $name The new name of the directory. | ||||
| 	 * @return void | ||||
| 	 */ | ||||
| 	public function setName($name) { | ||||
| 		logger('RedDirectory::setName(): ' . basename($this->red_path) . ' -> ' . $name, LOGGER_DATA); | ||||
|  | ||||
| 		if ((! $name) || (! $this->auth->owner_id)) { | ||||
| 			logger('RedDirectory::setName(): permission denied'); | ||||
| 			throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 		} | ||||
|  | ||||
| 		if (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage')) { | ||||
| 			logger('RedDirectory::setName(): permission denied'); | ||||
| 			throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 		} | ||||
|  | ||||
| 		list($parent_path, ) = DAV\URLUtil::splitPath($this->red_path); | ||||
| 		$new_path = $parent_path . '/' . $name; | ||||
|  | ||||
| 		$r = q("UPDATE attach SET filename = '%s' WHERE hash = '%s' AND uid = %d LIMIT 1", | ||||
| 			dbesc($name), | ||||
| 			dbesc($this->folder_hash), | ||||
| 			intval($this->auth->owner_id) | ||||
| 		); | ||||
|  | ||||
| 		$this->red_path = $new_path; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Creates a new file in the directory. | ||||
| 	 * | ||||
| 	 * Data will either be supplied as a stream resource, or in certain cases | ||||
| 	 * as a string. Keep in mind that you may have to support either. | ||||
| 	 * | ||||
| 	 * After successful creation of the file, you may choose to return the ETag | ||||
| 	 * of the new file here. | ||||
| 	 * | ||||
| 	 * @throws DAV\Exception\Forbidden | ||||
| 	 * @param string $name Name of the file | ||||
| 	 * @param resource|string $data Initial payload | ||||
| 	 * @return null|string ETag | ||||
| 	 */ | ||||
| 	public function createFile($name, $data = null) { | ||||
| 		logger('RedDirectory::createFile(): ' . $name, LOGGER_DATA); | ||||
|  | ||||
| 		if (! $this->auth->owner_id) { | ||||
| 			logger('RedDirectory::createFile(): permission denied'); | ||||
| 			throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 		} | ||||
|  | ||||
| 		if (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage')) { | ||||
| 			logger('RedDirectory::createFile(): permission denied'); | ||||
| 			throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 		} | ||||
|  | ||||
| 		$mimetype = z_mime_content_type($name); | ||||
|  | ||||
| 		$c = q("SELECT * FROM channel WHERE channel_id = %d AND NOT (channel_pageflags & %d) LIMIT 1", | ||||
| 			intval($this->auth->owner_id), | ||||
| 			intval(PAGE_REMOVED) | ||||
| 		); | ||||
|  | ||||
| 		if (! $c) { | ||||
| 			logger('RedDirectory::createFile(): no channel'); | ||||
| 			throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 		} | ||||
|  | ||||
| 		$filesize = 0; | ||||
| 		$hash = random_string(); | ||||
|  | ||||
| 		$r = q("INSERT INTO attach ( aid, uid, hash, creator, filename, folder, flags, filetype, filesize, revision, data, created, edited, allow_cid, allow_gid, deny_cid, deny_gid ) | ||||
| 			VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", | ||||
| 			intval($c[0]['channel_account_id']), | ||||
| 			intval($c[0]['channel_id']), | ||||
| 			dbesc($hash), | ||||
| 			dbesc($this->auth->observer), | ||||
| 			dbesc($name), | ||||
| 			dbesc($this->folder_hash), | ||||
| 			dbesc(ATTACH_FLAG_OS), | ||||
| 			dbesc($mimetype), | ||||
| 			intval($filesize), | ||||
| 			intval(0), | ||||
| 			dbesc($this->os_path . '/' . $hash), | ||||
| 			dbesc(datetime_convert()), | ||||
| 			dbesc(datetime_convert()), | ||||
| 			dbesc($c[0]['channel_allow_cid']), | ||||
| 			dbesc($c[0]['channel_allow_gid']), | ||||
| 			dbesc($c[0]['channel_deny_cid']), | ||||
| 			dbesc($c[0]['channel_deny_gid']) | ||||
| 		); | ||||
|  | ||||
| 		$f = 'store/' . $this->auth->owner_nick . '/' . (($this->os_path) ? $this->os_path . '/' : '') . $hash; | ||||
|  | ||||
| 		// returns the number of bytes that were written to the file, or FALSE on failure | ||||
| 		$size = file_put_contents($f, $data); | ||||
| 		// delete attach entry if file_put_contents() failed | ||||
| 		if ($size === false) { | ||||
| 			logger('RedDirectory::createFile(): file_put_contents() failed for ' . $name, LOGGER_DEBUG); | ||||
| 			attach_delete($c[0]['channel_id'], $hash); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		// returns now | ||||
| 		$edited = datetime_convert(); | ||||
|  | ||||
| 		// updates entry with filesize and timestamp | ||||
| 		$d = q("UPDATE attach SET filesize = '%s', edited = '%s' WHERE hash = '%s' AND uid = %d LIMIT 1", | ||||
| 			dbesc($size), | ||||
| 			dbesc($edited), | ||||
| 			dbesc($hash), | ||||
| 			intval($c[0]['channel_id']) | ||||
| 		); | ||||
|  | ||||
| 		// update the folder's lastmodified timestamp | ||||
| 		$e = q("UPDATE attach SET edited = '%s' WHERE hash = '%s' AND uid = %d LIMIT 1", | ||||
| 			dbesc($edited), | ||||
| 			dbesc($this->folder_hash), | ||||
| 			intval($c[0]['channel_id']) | ||||
| 		); | ||||
|  | ||||
| 		$maxfilesize = get_config('system', 'maxfilesize'); | ||||
| 		if (($maxfilesize) && ($size > $maxfilesize)) { | ||||
| 			attach_delete($c[0]['channel_id'], $hash); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		// check against service class quota | ||||
| 		$limit = service_class_fetch($c[0]['channel_id'], 'attach_upload_limit'); | ||||
| 		if ($limit !== false) { | ||||
| 			$x = q("SELECT SUM(filesize) AS total FROM attach WHERE aid = %d ", | ||||
| 				intval($c[0]['channel_account_id']) | ||||
| 			); | ||||
| 			if (($x) && ($x[0]['total'] + $size > $limit)) { | ||||
| 				logger('reddav: service class limit exceeded for ' . $c[0]['channel_name'] . ' total usage is ' . $x[0]['total'] . ' limit is ' . $limit); | ||||
| 				attach_delete($c[0]['channel_id'], $hash); | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Creates a new subdirectory. | ||||
| 	 * | ||||
| 	 * @param string $name the directory to create | ||||
| 	 * @return void | ||||
| 	 */ | ||||
| 	public function createDirectory($name) { | ||||
| 		logger('RedDirectory::createDirectory(): ' . $name, LOGGER_DEBUG); | ||||
|  | ||||
| 		if ((! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage'))) { | ||||
| 			throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 		} | ||||
|  | ||||
| 		$r = q("SELECT * FROM channel WHERE channel_id = %d AND NOT (channel_pageflags & %d) LIMIT 1", | ||||
| 			intval($this->auth->owner_id), | ||||
| 			intval(PAGE_REMOVED) | ||||
| 		); | ||||
|  | ||||
| 		if ($r) { | ||||
| 			$result = attach_mkdir($r[0], $this->auth->observer, array('filename' => $name, 'folder' => $this->folder_hash)); | ||||
| 			if (! $result['success']) { | ||||
| 				logger('RedDirectory::createDirectory(): ' . print_r($result, true), LOGGER_DEBUG); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Checks if a child exists. | ||||
| 	 * | ||||
| 	 * @param string $name | ||||
| 	 * @return boolean | ||||
| 	 */ | ||||
| 	public function childExists($name) { | ||||
| 		// On /cloud we show a list of available channels. | ||||
| 		// @todo what happens if no channels are available? | ||||
| 		if ($this->red_path === '/' && $name === 'cloud') { | ||||
| 			logger('RedDirectory::childExists() /cloud: true', LOGGER_DATA); | ||||
| 			return true; | ||||
| 		} | ||||
|  | ||||
| 		$x = RedFileData($this->ext_path . '/' . $name, $this->auth, true); | ||||
| 		logger('RedFileData returns: ' . print_r($x, true), LOGGER_DATA); | ||||
| 		if ($x) | ||||
| 			return true; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @todo add description of what this function does. | ||||
| 	 * | ||||
| 	 * @throw DAV\Exception\NotFound | ||||
| 	 * @return void | ||||
| 	 */ | ||||
| 	function getDir() { | ||||
| 		logger('RedDirectory::getDir(): ' . $this->ext_path, LOGGER_DEBUG); | ||||
| 		$this->auth->log(); | ||||
|  | ||||
| 		$file = $this->ext_path; | ||||
|  | ||||
| 		$x = strpos($file, '/cloud'); | ||||
| 		if ($x === false) | ||||
| 			return; | ||||
| 		if ($x === 0) { | ||||
| 			$file = substr($file, 6); | ||||
| 		} | ||||
|  | ||||
| 		if ((! $file) || ($file === '/')) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		$file = trim($file, '/'); | ||||
| 		$path_arr = explode('/', $file); | ||||
|  | ||||
| 		if (! $path_arr) | ||||
| 			return; | ||||
|  | ||||
| 		logger('RedDirectory::getDir(): path: ' . print_r($path_arr, true), LOGGER_DATA); | ||||
|  | ||||
| 		$channel_name = $path_arr[0]; | ||||
|  | ||||
| 		$r = q("SELECT channel_id FROM channel WHERE channel_address = '%s' AND NOT ( channel_pageflags & %d ) LIMIT 1", | ||||
| 			dbesc($channel_name), | ||||
| 			intval(PAGE_REMOVED) | ||||
| 		); | ||||
|  | ||||
| 		if (! $r) { | ||||
| 			throw new DAV\Exception\NotFound('The file with name: ' . $channel_name . ' could not be found.'); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		$channel_id = $r[0]['channel_id']; | ||||
| 		$this->auth->owner_id = $channel_id; | ||||
| 		$this->auth->owner_nick = $channel_name; | ||||
|  | ||||
| 		$path = '/' . $channel_name; | ||||
| 		$folder = ''; | ||||
| 		$os_path = ''; | ||||
|  | ||||
| 		for ($x = 1; $x < count($path_arr); $x++) { | ||||
| 			$r = q("select id, hash, filename, flags from attach where folder = '%s' and filename = '%s' and uid = %d and (flags & %d)", | ||||
| 				dbesc($folder), | ||||
| 				dbesc($path_arr[$x]), | ||||
| 				intval($channel_id), | ||||
| 				intval(ATTACH_FLAG_DIR) | ||||
| 			); | ||||
|  | ||||
| 			if ($r && ( $r[0]['flags'] & ATTACH_FLAG_DIR)) { | ||||
| 				$folder = $r[0]['hash']; | ||||
| 				if (strlen($os_path)) | ||||
| 					$os_path .= '/'; | ||||
| 				$os_path .= $folder; | ||||
|  | ||||
| 				$path = $path . '/' . $r[0]['filename']; | ||||
| 			} | ||||
| 		} | ||||
| 		$this->folder_hash = $folder; | ||||
| 		$this->os_path = $os_path; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Returns the last modification time for the directory, as a UNIX | ||||
| 	 *        timestamp. | ||||
| 	 * | ||||
| 	 * It looks for the last edited file in the folder. If it is an empty folder | ||||
| 	 * it returns the lastmodified time of the folder itself, to prevent zero | ||||
| 	 * timestamps. | ||||
| 	 * | ||||
| 	 * @return int last modification time in UNIX timestamp | ||||
| 	 */ | ||||
| 	public function getLastModified() { | ||||
| 		$r = q("SELECT edited FROM attach WHERE folder = '%s' AND uid = %d ORDER BY edited DESC LIMIT 1", | ||||
| 			dbesc($this->folder_hash), | ||||
| 			intval($this->auth->owner_id) | ||||
| 		); | ||||
| 		if (! $r) { | ||||
| 			$r = q("SELECT edited FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1", | ||||
| 				dbesc($this->folder_hash), | ||||
| 				intval($this->auth->owner_id) | ||||
| 			); | ||||
| 			if (! $r) | ||||
| 				return ''; | ||||
| 		} | ||||
| 		return datetime_convert('UTC', 'UTC', $r[0]['edited'], 'U'); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Return quota usage. | ||||
| 	 * | ||||
| 	 * Do guests relly see the used/free values from filesystem of the complete store directory? | ||||
| 	 * | ||||
| 	 * @return array with used and free values in bytes. | ||||
| 	 */ | ||||
| 	public function getQuotaInfo() { | ||||
| 		// values from the filesystem of the complete <i>store/</i> directory | ||||
| 		$limit = disk_total_space('store'); | ||||
| 		$free = disk_free_space('store'); | ||||
|  | ||||
| 		if ($this->auth->owner_id) { | ||||
| 			$c = q("select * from channel where channel_id = %d and not (channel_pageflags & %d) limit 1", | ||||
| 				intval($this->auth->owner_id), | ||||
| 				intval(PAGE_REMOVED) | ||||
| 			); | ||||
|  | ||||
| 			$ulimit = service_class_fetch($c[0]['channel_id'], 'attach_upload_limit'); | ||||
| 			$limit = (($ulimit) ? $ulimit : $limit); | ||||
|  | ||||
| 			$x = q("select sum(filesize) as total from attach where aid = %d", | ||||
| 				intval($c[0]['channel_account_id']) | ||||
| 			); | ||||
| 			$free = (($x) ? $limit - $x[0]['total'] : 0); | ||||
| 		} | ||||
|  | ||||
| 		return array( | ||||
| 			$limit - $free, | ||||
| 			$free | ||||
| 		); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										275
									
								
								include/RedDAV/RedFile.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								include/RedDAV/RedFile.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,275 @@ | ||||
| <?php | ||||
|  | ||||
| namespace RedMatrix\RedDAV; | ||||
|  | ||||
| use Sabre\DAV; | ||||
|  | ||||
| /** | ||||
|  * @brief This class represents a file in DAV. | ||||
|  * | ||||
|  * It provides all functions to work with files in Red's cloud through DAV protocol. | ||||
|  * | ||||
|  * @extends \Sabre\DAV\Node | ||||
|  * @implements \Sabre\DAV\IFile | ||||
|  * | ||||
|  * @link http://github.com/friendica/red | ||||
|  * @license http://opensource.org/licenses/mit-license.php The MIT License (MIT) | ||||
|  */ | ||||
| class RedFile extends DAV\Node implements DAV\IFile { | ||||
|  | ||||
| 	/** | ||||
| 	 * The file from attach table. | ||||
| 	 * | ||||
| 	 * @var array | ||||
| 	 *  data | ||||
| 	 *  flags | ||||
| 	 *  filename (string) | ||||
| 	 *  filetype (string) | ||||
| 	 */ | ||||
| 	private $data; | ||||
| 	/** | ||||
| 	 * @see \Sabre\DAV\Auth\Backend\BackendInterface | ||||
| 	 * @var \RedMatrix\RedDAV\RedBasicAuth | ||||
| 	 */ | ||||
| 	private $auth; | ||||
| 	/** | ||||
| 	 * @var string | ||||
| 	 */ | ||||
| 	private $name; | ||||
|  | ||||
| 	/** | ||||
| 	 * Sets up the node, expects a full path name. | ||||
| 	 * | ||||
| 	 * @param string $name | ||||
| 	 * @param array $data from attach table | ||||
| 	 * @param &$auth | ||||
| 	 */ | ||||
| 	public function __construct($name, $data, &$auth) { | ||||
| 		$this->name = $name; | ||||
| 		$this->data = $data; | ||||
| 		$this->auth = $auth; | ||||
|  | ||||
| 		logger('RedFile::__construct(): ' . print_r($this->data, true), LOGGER_DATA); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Returns the name of the file. | ||||
| 	 * | ||||
| 	 * @return string | ||||
| 	 */ | ||||
| 	public function getName() { | ||||
| 		logger('RedFile::getName(): ' . basename($this->name), LOGGER_DEBUG); | ||||
| 		return basename($this->name); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Renames the file. | ||||
| 	 * | ||||
| 	 * @throw Sabre\DAV\Exception\Forbidden | ||||
| 	 * @param string $name The new name of the file. | ||||
| 	 * @return void | ||||
| 	 */ | ||||
| 	public function setName($newName) { | ||||
| 		logger('RedFile::setName(): ' . basename($this->name) . ' -> ' . $newName, LOGGER_DEBUG); | ||||
|  | ||||
| 		if ((! $newName) || (! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage'))) { | ||||
| 			throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 		} | ||||
|  | ||||
| 		$newName = str_replace('/', '%2F', $newName); | ||||
|  | ||||
| 		$r = q("UPDATE attach SET filename = '%s' WHERE hash = '%s' AND id = %d LIMIT 1", | ||||
| 			dbesc($this->data['filename']), | ||||
| 			intval($this->data['id']) | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Updates the data of the file. | ||||
| 	 * | ||||
| 	 * @param resource $data | ||||
| 	 * @return void | ||||
| 	 */ | ||||
| 	public function put($data) { | ||||
| 		logger('RedFile::put(): ' . basename($this->name), LOGGER_DEBUG); | ||||
| 		$size = 0; | ||||
|  | ||||
| 		// @todo only 3 values are needed | ||||
| 		$c = q("SELECT * FROM channel WHERE channel_id = %d AND NOT (channel_pageflags & %d) LIMIT 1", | ||||
| 			intval($this->auth->owner_id), | ||||
| 			intval(PAGE_REMOVED) | ||||
| 		); | ||||
|  | ||||
| 		$r = q("SELECT flags, folder, data FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1", | ||||
| 			dbesc($this->data['hash']), | ||||
| 			intval($c[0]['channel_id']) | ||||
| 		); | ||||
| 		if ($r) { | ||||
| 			if ($r[0]['flags'] & ATTACH_FLAG_OS) { | ||||
| 				$f = 'store/' . $this->auth->owner_nick . '/' . (($r[0]['data']) ? $r[0]['data'] : ''); | ||||
| 				// @todo check return value and set $size directly | ||||
| 				@file_put_contents($f, $data); | ||||
| 				$size = @filesize($f); | ||||
| 				logger('RedFile::put(): filename: ' . $f . ' size: ' . $size, LOGGER_DEBUG); | ||||
| 			} else { | ||||
| 				$r = q("UPDATE attach SET data = '%s' WHERE hash = '%s' AND uid = %d LIMIT 1", | ||||
| 					dbesc(stream_get_contents($data)), | ||||
| 					dbesc($this->data['hash']), | ||||
| 					intval($this->data['uid']) | ||||
| 				); | ||||
| 				$r = q("SELECT length(data) AS fsize FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1", | ||||
| 					dbesc($this->data['hash']), | ||||
| 					intval($this->data['uid']) | ||||
| 				); | ||||
| 				if ($r) { | ||||
| 					$size = $r[0]['fsize']; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// returns now() | ||||
| 		$edited = datetime_convert(); | ||||
|  | ||||
| 		$d = q("UPDATE attach SET filesize = '%s', edited = '%s' WHERE hash = '%s' AND uid = %d LIMIT 1", | ||||
| 			dbesc($size), | ||||
| 			dbesc($edited), | ||||
| 			dbesc($this->data['hash']), | ||||
| 			intval($c[0]['channel_id']) | ||||
| 		); | ||||
|  | ||||
| 		// update the folder's lastmodified timestamp | ||||
| 		$e = q("UPDATE attach SET edited = '%s' WHERE hash = '%s' AND uid = %d LIMIT 1", | ||||
| 			dbesc($edited), | ||||
| 			dbesc($r[0]['folder']), | ||||
| 			intval($c[0]['channel_id']) | ||||
| 		); | ||||
|  | ||||
| 		// @todo do we really want to remove the whole file if an update fails | ||||
| 		// because of maxfilesize or quota? | ||||
| 		// There is an Exception "InsufficientStorage" or "PaymentRequired" for | ||||
| 		// our service class from SabreDAV we could use. | ||||
|  | ||||
| 		$maxfilesize = get_config('system', 'maxfilesize'); | ||||
| 		if (($maxfilesize) && ($size > $maxfilesize)) { | ||||
| 			attach_delete($c[0]['channel_id'], $this->data['hash']); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		$limit = service_class_fetch($c[0]['channel_id'], 'attach_upload_limit'); | ||||
| 		if ($limit !== false) { | ||||
| 			$x = q("select sum(filesize) as total from attach where aid = %d ", | ||||
| 				intval($c[0]['channel_account_id']) | ||||
| 			); | ||||
| 			if (($x) && ($x[0]['total'] + $size > $limit)) { | ||||
| 				logger('RedFile::put(): service class limit exceeded for ' . $c[0]['channel_name'] . ' total usage is ' . $x[0]['total'] . ' limit is ' . $limit); | ||||
| 				attach_delete($c[0]['channel_id'], $this->data['hash']); | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Returns the raw data. | ||||
| 	 * | ||||
| 	 * @return string | ||||
| 	 */ | ||||
| 	public function get() { | ||||
| 		logger('RedFile::get(): ' . basename($this->name), LOGGER_DEBUG); | ||||
|  | ||||
| 		$r = q("SELECT data, flags, filename, filetype FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1", | ||||
| 			dbesc($this->data['hash']), | ||||
| 			intval($this->data['uid']) | ||||
| 		); | ||||
| 		if ($r) { | ||||
| 			// @todo this should be a global definition | ||||
| 			$unsafe_types = array('text/html', 'text/css', 'application/javascript'); | ||||
|  | ||||
| 			if (in_array($r[0]['filetype'], $unsafe_types)) { | ||||
| 				header('Content-disposition: attachment; filename="' . $r[0]['filename'] . '"'); | ||||
| 				header('Content-type: text/plain'); | ||||
| 			} | ||||
|  | ||||
| 			if ($r[0]['flags'] & ATTACH_FLAG_OS ) { | ||||
| 				$f = 'store/' . $this->auth->owner_nick . '/' . (($this->os_path) ? $this->os_path . '/' : '') . $r[0]['data']; | ||||
| 				return fopen($f, 'rb'); | ||||
| 			} | ||||
| 			return $r[0]['data']; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief 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 arbitrary string, but MUST be surrounded by double-quotes. | ||||
| 	 * | ||||
| 	 * Return null if the ETag can not effectively be determined. | ||||
| 	 * | ||||
| 	 * @return mixed | ||||
| 	 */ | ||||
| 	public function getETag() { | ||||
| 		$ret = null; | ||||
| 		if ($this->data['hash']) { | ||||
| 			$ret = '"' . $this->data['hash'] . '"'; | ||||
| 		} | ||||
| 		return $ret; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Returns the mime-type for a file. | ||||
| 	 * | ||||
| 	 * If null is returned, we'll assume application/octet-stream | ||||
| 	 * | ||||
| 	 * @return mixed | ||||
| 	 */ | ||||
| 	public function getContentType() { | ||||
| 		// @todo this should be a global definition. | ||||
| 		$unsafe_types = array('text/html', 'text/css', 'application/javascript'); | ||||
| 		if (in_array($this->data['filetype'], $unsafe_types)) { | ||||
| 			return 'text/plain'; | ||||
| 		} | ||||
| 		return $this->data['filetype']; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Returns the size of the node, in bytes. | ||||
| 	 * | ||||
| 	 * @return int | ||||
| 	 */ | ||||
| 	public function getSize() { | ||||
| 		return $this->data['filesize']; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Returns the last modification time for the file, as a unix | ||||
| 	 *        timestamp. | ||||
| 	 * | ||||
| 	 * @return int last modification time in UNIX timestamp | ||||
| 	 */ | ||||
| 	public function getLastModified() { | ||||
| 		return datetime_convert('UTC', 'UTC', $this->data['edited'], 'U'); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @brief Delete the file. | ||||
| 	 * | ||||
| 	 * @throw Sabre\DAV\Exception\Forbidden | ||||
| 	 * @return void | ||||
| 	 */ | ||||
| 	public function delete() { | ||||
| 		logger('RedFile::delete(): ' . basename($this->name), LOGGER_DEBUG); | ||||
|  | ||||
| 		if ((! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage'))) { | ||||
| 			throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 		} | ||||
|  | ||||
| 		if ($this->auth->owner_id !== $this->auth->channel_id) { | ||||
| 			if (($this->auth->observer !== $this->data['creator']) || ($this->data['flags'] & ATTACH_FLAG_DIR)) { | ||||
| 				throw new DAV\Exception\Forbidden('Permission denied.'); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		attach_delete($this->auth->owner_id, $this->data['hash']); | ||||
| 	} | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -63,7 +63,7 @@ function cloud_init(&$a) { | ||||
| 	if ($which) | ||||
| 		profile_load($a, $which, $profile); | ||||
|  | ||||
| 	$auth = new RedBasicAuth(); | ||||
| 	$auth = new RedDAV\RedBasicAuth(); | ||||
|  | ||||
| 	$ob_hash = get_observer_hash(); | ||||
|  | ||||
| @@ -91,7 +91,7 @@ function cloud_init(&$a) { | ||||
| 	$_SERVER['REQUEST_URI'] = strip_zids($_SERVER['REQUEST_URI']); | ||||
| 	$_SERVER['REQUEST_URI'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism', '', $_SERVER['REQUEST_URI']); | ||||
|  | ||||
| 	$rootDirectory = new RedDirectory('/', $auth); | ||||
| 	$rootDirectory = new RedDAV\RedDirectory('/', $auth); | ||||
|  | ||||
| 	// A SabreDAV server-object | ||||
| 	$server = new DAV\Server($rootDirectory); | ||||
| @@ -117,7 +117,7 @@ function cloud_init(&$a) { | ||||
| 	if ((! $auth->observer) && ($_SERVER['REQUEST_METHOD'] === 'GET')) { | ||||
| 		try {  | ||||
| 			$x = RedFileData('/' . $a->cmd, $auth); | ||||
| 			if($x instanceof RedFile) | ||||
| 			if($x instanceof RedDAV\RedFile) | ||||
| 				$isapublic_file = true; | ||||
| 		} | ||||
| 		catch (Exception $e) { | ||||
| @@ -150,4 +150,4 @@ function cloud_init(&$a) { | ||||
| 	$server->exec(); | ||||
|  | ||||
| 	killme(); | ||||
| } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user