diff options
Diffstat (limited to 'include')
31 files changed, 1824 insertions, 1263 deletions
diff --git a/include/Contact.php b/include/Contact.php index 4440369dc..20e5e1a1e 100644 --- a/include/Contact.php +++ b/include/Contact.php @@ -245,11 +245,13 @@ function channel_remove($channel_id, $local = true, $unset_session=true) { intval($channel_id) ); + $r = q("update hubloc set hubloc_flags = (hubloc_flags | %d) where hubloc_hash = '%s'", intval(HUBLOC_FLAGS_DELETED), dbesc($channel['channel_hash']) ); + $r = q("update xchan set xchan_flags = (xchan_flags | %d) where xchan_hash = '%s'", intval(XCHAN_FLAGS_DELETED), dbesc($channel['channel_hash']) @@ -257,7 +259,6 @@ function channel_remove($channel_id, $local = true, $unset_session=true) { proc_run('php','include/notifier.php','purge_all',$channel_id); - } q("DELETE FROM `groups` WHERE `uid` = %d", intval($channel_id)); @@ -291,11 +292,23 @@ function channel_remove($channel_id, $local = true, $unset_session=true) { dbesc(z_root()) ); - $r = q("update xchan set xchan_flags = (xchan_flags | %d) where xchan_hash = '%s' ", - intval(XCHAN_FLAGS_DELETED), - dbesc($channel['channel_hash']) + // Do we have any valid hublocs remaining? + + $hublocs = 0; + + $r = q("select hubloc_id from hubloc where hubloc_hash = '%s' and not (hubloc_flags & %d)", + dbesc($channel['channel_hash']), + intval(HUBLOC_FLAGS_DELETED) ); + if($r) + $hublocs = count($r); + if(! $hublocs) { + $r = q("update xchan set xchan_flags = (xchan_flags | %d) where xchan_hash = '%s' ", + intval(XCHAN_FLAGS_DELETED), + dbesc($channel['channel_hash']) + ); + } proc_run('php','include/directory.php',$channel_id); diff --git a/include/ItemObject.php b/include/ItemObject.php index 7c75e35bd..90c036def 100644 --- a/include/ItemObject.php +++ b/include/ItemObject.php @@ -191,7 +191,8 @@ class Item extends BaseObject { } - $verified = (($item['item_flags'] & ITEM_VERIFIED) ? t('Message is verified') : ''); + $verified = (($item['item_flags'] & ITEM_VERIFIED) ? t('Message signature validated') : ''); + $forged = ((($item['sig']) && (! ($item['item_flags'] & ITEM_VERIFIED))) ? t('Message signature incorrect') : ''); $unverified = '' ; // (($this->is_wall_to_wall() && (! ($item['item_flags'] & ITEM_VERIFIED))) ? t('Message cannot be verified') : ''); @@ -269,6 +270,7 @@ class Item extends BaseObject { 'lock' => $lock, 'verified' => $verified, 'unverified' => $unverified, + 'forged' => $forged, 'location' => $location, 'indent' => $indent, 'owner_url' => $this->get_owner_url(), diff --git a/include/RedDAV/RedBasicAuth.php b/include/RedDAV/RedBasicAuth.php new file mode 100644 index 000000000..2f86d4f82 --- /dev/null +++ b/include/RedDAV/RedBasicAuth.php @@ -0,0 +1,209 @@ +<?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 \Sabre\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('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('password verified for ' . $username); + return $this->setAuthenticated($r[0]); + } + } + } + } + logger('password failed for ' . $username); + // @TODO add security logger + 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 \Sabre\DAV\Browser\Plugin $browser + */ + public function setBrowserPlugin($browser) { + $this->browser = $browser; + } + + /** + * @brief Prints out all RedBasicAuth variables to logger(). + * + * @return void + */ + public function log() { + logger('channel_name ' . $this->channel_name, LOGGER_DATA); + logger('channel_id ' . $this->channel_id, LOGGER_DATA); + logger('channel_hash ' . $this->channel_hash, LOGGER_DATA); + logger('observer ' . $this->observer, LOGGER_DATA); + logger('owner_id ' . $this->owner_id, LOGGER_DATA); + logger('owner_nick ' . $this->owner_nick, LOGGER_DATA); + } +}
\ No newline at end of file diff --git a/include/RedDAV/RedBrowser.php b/include/RedDAV/RedBrowser.php index 6639250ae..21ea76aed 100644 --- a/include/RedDAV/RedBrowser.php +++ b/include/RedDAV/RedBrowser.php @@ -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) { @@ -256,9 +259,18 @@ class RedBrowser extends DAV\Browser\Plugin { $html .= $output; get_app()->page['content'] = $html; + load_pdl(get_app()); construct_page(get_app()); } + /** + * @brief Returns a human readable formatted string for filesizes. + * + * Don't we need such a functionality in other places, too? + * + * @param int $size filesize in bytes + * @return string + */ function userReadableSize($size) { $ret = ""; if (is_numeric($size)) { diff --git a/include/RedDAV/RedDirectory.php b/include/RedDAV/RedDirectory.php new file mode 100644 index 000000000..a46b77f5f --- /dev/null +++ b/include/RedDAV/RedDirectory.php @@ -0,0 +1,462 @@ +<?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('directory ' . $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('ext_path ' . $this->ext_path, LOGGER_DATA); + logger('os_path ' . $this->os_path, LOGGER_DATA); + logger('red_path ' . $this->red_path, LOGGER_DATA); + } + + /** + * @brief Returns an array with all the child nodes. + * + * @throw \Sabre\DAV\Exception\Forbidden + * @return array \Sabre\DAV\INode[] + */ + public function getChildren() { + //logger('children 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 \Sabre\DAV\Exception\Forbidden + * @throw \Sabre\DAV\Exception\NotFound + * @param string $name + */ + public function getChild($name) { + logger($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(basename($this->red_path), LOGGER_DATA); + return (basename($this->red_path)); + } + + /** + * @brief Renames the directory. + * + * @todo handle duplicate directory name + * + * @throw \Sabre\DAV\Exception\Forbidden + * @param string $name The new name of the directory. + * @return void + */ + public function setName($name) { + logger('old name ' . basename($this->red_path) . ' -> ' . $name, LOGGER_DATA); + + if ((! $name) || (! $this->auth->owner_id)) { + logger('permission denied ' . $name); + throw new DAV\Exception\Forbidden('Permission denied.'); + } + + if (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage')) { + logger('permission denied '. $name); + 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. + * + * @throw \Sabre\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($name, LOGGER_DEBUG); + + if (! $this->auth->owner_id) { + logger('permission denied ' . $name); + throw new DAV\Exception\Forbidden('Permission denied.'); + } + + if (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage')) { + logger('permission denied ' . $name); + 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('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('file_put_contents() failed to ' . $f); + 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('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($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('error ' . print_r($result, true), LOGGER_DEBUG); + } + } + } + + /** + * @brief Checks if a child exists. + * + * @param string $name + * The name to check if it exists. + * @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('We are at /cloud show a channel list', LOGGER_DEBUG); + 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 \Sabre\DAV\Exception\NotFound + * @return void + */ + function getDir() { + //logger($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('paths: ' . 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.'); + } + + $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; + } + + /** + * @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. + * + * @fixme Should 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 + ); + } +}
\ No newline at end of file diff --git a/include/RedDAV/RedFile.php b/include/RedDAV/RedFile.php new file mode 100644 index 000000000..f96790631 --- /dev/null +++ b/include/RedDAV/RedFile.php @@ -0,0 +1,279 @@ +<?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(print_r($this->data, true), LOGGER_DATA); + } + + /** + * @brief Returns the name of the file. + * + * @return string + */ + public function getName() { + //logger(basename($this->name), LOGGER_DATA); + 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('old name ' . basename($this->name) . ' -> ' . $newName, LOGGER_DATA); + + if ((! $newName) || (! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage'))) { + logger('permission denied '. $newName); + 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('put file: ' . 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('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('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('get file ' . 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 null|string + */ + 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 + * filesize in bytes + */ + 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. + * + * This method checks the permissions and then calls attach_delete() function + * to actually remove the file. + * + * @throw \Sabre\DAV\Exception\Forbidden + */ + public function delete() { + logger('delete file ' . 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']); + } +}
\ No newline at end of file diff --git a/include/acl_selectors.php b/include/acl_selectors.php index 0b68ba227..5adafff2c 100644 --- a/include/acl_selectors.php +++ b/include/acl_selectors.php @@ -248,8 +248,7 @@ function populate_acl($defaults = null,$show_jotnets = true) { '$aclModalTitle' => t('Permissions'), '$aclModalDismiss' => t('Close') )); - - + return $o; } diff --git a/include/api.php b/include/api.php index 2505def4c..9fe2ef47d 100644 --- a/include/api.php +++ b/include/api.php @@ -197,7 +197,10 @@ require_once('include/items.php'); case "json": header ("Content-Type: application/json"); foreach($r as $rr) - return json_encode($rr); + $json = json_encode($rr); + if ($_GET['callback']) + $json = $_GET['callback']."(".$json.")"; + return $json; break; case "rss": header ("Content-Type: application/rss+xml"); @@ -618,7 +621,19 @@ require_once('include/items.php'); api_register_func('api/red/group','api_group', true); + function api_red_xchan(&$a,$type) { + if(api_user() === false) + return false; + require_once('include/hubloc.php'); + if($_SERVER['request_method'] === 'POST') { + $r = xchan_store($_REQUEST); + } + $r = xchan_fetch($_REQUEST); + json_return_and_die($r); + }; + api_register_func('api/red/xchan','api_red_xchan',true); + function api_statuses_mediap(&$a, $type) { if (api_user() === false) { diff --git a/include/attach.php b/include/attach.php index 0df2e82a5..6bce617cd 100644 --- a/include/attach.php +++ b/include/attach.php @@ -26,77 +26,74 @@ function z_mime_content_type($filename) { $mime_types = array( - 'txt' => 'text/plain', - 'htm' => 'text/html', - 'html' => 'text/html', - 'php' => 'text/html', - 'css' => 'text/css', - 'js' => 'application/javascript', - 'json' => 'application/json', - 'xml' => 'application/xml', - 'swf' => 'application/x-shockwave-flash', - 'flv' => 'video/x-flv', - 'epub' => 'application/epub+zip', - - // images - 'png' => 'image/png', - 'jpe' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'jpg' => 'image/jpeg', - 'gif' => 'image/gif', - 'bmp' => 'image/bmp', - 'ico' => 'image/vnd.microsoft.icon', - 'tiff' => 'image/tiff', - 'tif' => 'image/tiff', - 'svg' => 'image/svg+xml', - 'svgz' => 'image/svg+xml', - - // archives - 'zip' => 'application/zip', - 'rar' => 'application/x-rar-compressed', - 'exe' => 'application/x-msdownload', - 'msi' => 'application/x-msdownload', - 'cab' => 'application/vnd.ms-cab-compressed', - - // audio/video - 'mp3' => 'audio/mpeg', - 'wav' => 'audio/wav', - 'qt' => 'video/quicktime', - 'mov' => 'video/quicktime', - 'ogg' => 'application/ogg', - - // adobe - 'pdf' => 'application/pdf', - 'psd' => 'image/vnd.adobe.photoshop', - 'ai' => 'application/postscript', - 'eps' => 'application/postscript', - 'ps' => 'application/postscript', - - // ms office - 'doc' => 'application/msword', - 'rtf' => 'application/rtf', - 'xls' => 'application/vnd.ms-excel', - 'ppt' => 'application/vnd.ms-powerpoint', - - - // open office - 'odt' => 'application/vnd.oasis.opendocument.text', - 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'txt' => 'text/plain', + 'htm' => 'text/html', + 'html' => 'text/html', + 'php' => 'text/html', + 'css' => 'text/css', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'xml' => 'application/xml', + 'swf' => 'application/x-shockwave-flash', + 'flv' => 'video/x-flv', + 'epub' => 'application/epub+zip', + + // images + 'png' => 'image/png', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'gif' => 'image/gif', + 'bmp' => 'image/bmp', + 'ico' => 'image/vnd.microsoft.icon', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + + // archives + 'zip' => 'application/zip', + 'rar' => 'application/x-rar-compressed', + 'exe' => 'application/x-msdownload', + 'msi' => 'application/x-msdownload', + 'cab' => 'application/vnd.ms-cab-compressed', + + // audio/video + 'mp3' => 'audio/mpeg', + 'wav' => 'audio/wav', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'ogg' => 'application/ogg', + + // adobe + 'pdf' => 'application/pdf', + 'psd' => 'image/vnd.adobe.photoshop', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + + // ms office + 'doc' => 'application/msword', + 'rtf' => 'application/rtf', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + + // open office + 'odt' => 'application/vnd.oasis.opendocument.text', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', ); - $dot = strpos($filename,'.'); - if($dot !== false) { - $ext = strtolower(substr($filename,$dot+1)); + $dot = strpos($filename, '.'); + if ($dot !== false) { + $ext = strtolower(substr($filename, $dot + 1)); if (array_key_exists($ext, $mime_types)) { return $mime_types[$ext]; } } return 'application/octet-stream'; - } - /** * @brief Count files/attachments. * @@ -138,8 +135,8 @@ function attach_count_files($channel_id, $observer, $hash = '', $filename = '', $ret['success'] = ((is_array($r)) ? true : false); $ret['results'] = ((is_array($r)) ? count($r) : false); - return $ret; + return $ret; } /** @@ -190,8 +187,8 @@ function attach_list_files($channel_id, $observer, $hash = '', $filename = '', $ $ret['success'] = ((is_array($r)) ? true : false); $ret['results'] = ((is_array($r)) ? $r : false); - return $ret; + return $ret; } /** @@ -246,8 +243,8 @@ function attach_by_hash($hash, $rev = 0) { $ret['success'] = true; $ret['data'] = $r[0]; - return $ret; + return $ret; } /** @@ -301,7 +298,6 @@ function attach_by_hash_nodata($hash, $rev = 0) { $ret['success'] = true; $ret['data'] = $r[0]; return $ret; - } /** @@ -400,6 +396,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { if(! isset($hash)) $hash = random_string(); + $created = datetime_convert(); if($options === 'replace') { @@ -432,7 +429,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { dbesc($x[0]['deny_cid']), dbesc($x[0]['deny_gid']) ); - } + } elseif($options === 'update') { $r = q("update attach set filename = '%s', filetype = '%s', edited = '%s', allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s' where id = %d and uid = %d limit 1", @@ -446,7 +443,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { intval($x[0]['id']), intval($x[0]['uid']) ); - } + } else { $r = q("INSERT INTO attach ( aid, uid, hash, creator, filename, filetype, filesize, revision, data, created, edited, allow_cid, allow_gid,deny_cid, deny_gid ) VALUES ( %d, %d, '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", @@ -466,7 +463,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { dbesc(($arr && array_key_exists('deny_cid',$arr)) ? $arr['deny_cid'] : ''), dbesc(($arr && array_key_exists('deny_gid',$arr)) ? $arr['deny_gid'] : '') ); - } + } if($options !== 'update') @unlink($src); @@ -490,6 +487,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { $ret['success'] = true; $ret['data'] = $r[0]; + return $ret; } @@ -507,8 +505,8 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { * $ret['data'] = array of attach DB entries without data component */ function z_readdir($channel_id, $observer_hash, $pathname, $parent_hash = '') { - $ret = array('success' => false); + if(! perm_is_allowed($r[0]['uid'], get_observer_hash(), 'view_storage')) { $ret['message'] = t('Permission denied.'); return $ret; @@ -547,6 +545,7 @@ function z_readdir($channel_id, $observer_hash, $pathname, $parent_hash = '') { } $ret['success'] = true; $ret['data'] = $r; + return $ret; } @@ -686,7 +685,6 @@ function attach_mkdir($channel, $observer_hash, $arr = null) { } return $ret; - } /** @@ -732,15 +730,19 @@ function attach_change_permissions($channel_id, $resource, $allow_cid, $allow_gi dbesc($resource), intval($channel_id) ); - - return; } /** - * @brief Delete a file/directory. - * + * @brief Delete a file/directory from a channel. + * + * If the provided resource hash is from a directory it will delete everything + * recursively under this directory. + * * @param int $channel_id - * @param string $resource a hash to delete + * The id of the channel + * @param string $resource + * The hash to delete + * @return void */ function attach_delete($channel_id, $resource) { @@ -760,7 +762,7 @@ function attach_delete($channel_id, $resource) { // If resource is a directory delete everything in the directory recursive if($r[0]['flags'] & ATTACH_FLAG_DIR) { - $x = q("select hash, flags from attach where folder = '%s' and uid = %d", + $x = q("SELECT hash, flags FROM attach WHERE folder = '%s' AND uid = %d", dbesc($resource), intval($channel_id) ); @@ -799,19 +801,21 @@ function attach_delete($channel_id, $resource) { dbesc($r[0]['folder']), intval($channel_id) ); - - return; } /** * @brief Returns path to file in cloud/. * - * @param $arr - * @return string with the path the file to cloud/ + * @param array + * $arr[uid] int the channels uid + * $arr[folder] string + * $arr[filename]] string + * @return string + * path to the file in cloud/ */ function get_cloudpath($arr) { - $basepath = 'cloud/'; + if($arr['uid']) { $r = q("select channel_address from channel where channel_id = %d limit 1", intval($arr['uid']) @@ -823,7 +827,6 @@ function get_cloudpath($arr) { $path = $basepath; if($arr['folder']) { - $lpath = ''; $lfile = $arr['folder']; @@ -842,60 +845,83 @@ function get_cloudpath($arr) { $lpath = $r[0]['filename'] . '/' . $lpath; $lfile = $r[0]['folder']; - } while ( ($r[0]['folder']) && ($r[0]['flags'] & ATTACH_FLAG_DIR)) ; + } while ( ($r[0]['folder']) && ($r[0]['flags'] & ATTACH_FLAG_DIR)); - $path .= $lpath; + $path .= $lpath; } - $path .= $arr['filename']; + return $path; } /** * @brief Returns path to parent folder in cloud/. - * - * @param $arr - * @return string with the folder path + * + * @param int $channel_id + * The id of the channel + * @param string $channel_name + * The name of the channel + * @param string $attachHash + * @return string with the full folder path */ function get_parent_cloudpath($channel_id, $channel_name, $attachHash) { - //Build directory tree and redirect + // build directory tree $parentHash = $attachHash; do { $parentHash = find_folder_hash_by_attach_hash($channel_id, $parentHash); if ($parentHash) { $parentName = find_filename_by_hash($channel_id, $parentHash); - $parentFullPath = $parentName."/".$parentFullPath; + $parentFullPath = $parentName . '/' . $parentFullPath; } } while ($parentHash); - $parentFullPath = z_root() . "/cloud/" . $channel_name . "/" . $parentFullPath; + $parentFullPath = z_root() . '/cloud/' . $channel_name . '/' . $parentFullPath; + return $parentFullPath; } + +/** + * @brief Return the hash of an attachment's folder. + * + * @param int $channel_id + * The id of the channel + * @param string $attachHash + * The hash of the attachment + * @return string + */ function find_folder_hash_by_attach_hash($channel_id, $attachHash) { - $r = q("select * from attach where uid = %d and hash = '%s' limit 1", - intval($channel_id), dbesc($attachHash) + $r = q("SELECT folder FROM attach WHERE uid = %d AND hash = '%s' LIMIT 1", + intval($channel_id), + dbesc($attachHash) ); - $hash = ""; - if($r) { - foreach($r as $rr) { - $hash = $rr['folder']; - } + $hash = ''; + if ($r) { + $hash = $r[0]['folder']; } - return $hash; + return $hash; } + +/** + * @brief Returns the filename of an attachment in a given channel. + * + * @param mixed $channel_id + * The id of the channel + * @param mixed $attachHash + * The hash of the attachment + * @return string + * The filename of the attachment + */ function find_filename_by_hash($channel_id, $attachHash) { - $r = q("select * from attach where uid = %d and hash = '%s' limit 1", - intval($channel_id), dbesc($attachHash) + $r = q("SELECT filename FROM attach WHERE uid = %d AND hash = '%s' LIMIT 1", + intval($channel_id), + dbesc($attachHash) ); - $filename = ""; - if($r) { - foreach($r as $rr) { - $filename = $rr['filename']; - } + $filename = ''; + if ($r) { + $filename = $r[0]['filename']; } - return $filename; + return $filename; } - /** * * @param $in @@ -904,6 +930,6 @@ function find_filename_by_hash($channel_id, $attachHash) { function pipe_streams($in, $out) { $size = 0; while (!feof($in)) - $size += fwrite($out, fread($in,8192)); + $size += fwrite($out, fread($in, 8192)); return $size; } diff --git a/include/bb2diaspora.php b/include/bb2diaspora.php index a80b3343b..8a178d1ac 100644 --- a/include/bb2diaspora.php +++ b/include/bb2diaspora.php @@ -116,6 +116,8 @@ function diaspora_mention_callback($matches) { function diaspora2bb($s,$use_zrl = false) { + $s = str_replace("
\n>","",$s); + $s = html_entity_decode($s,ENT_COMPAT,'UTF-8'); // Too many new lines. So deactivated the following line @@ -286,6 +288,23 @@ function bb2diaspora_itemwallwall(&$item) { . '[url=' . $item['author']['xchan_url'] . ']' . $item['author']['xchan_name'] . '[/url]' . "\n\n" . $item['body']; } + + // We have to do something similar for wall-to-wall comments. ITEM_WALL|ITEM_ORIGIN indicates that it was posted on this site. + // Regular matrix comments may have one of these bits set, but not both. + + // Update: this is getting triggered way too often and unnecessarily. Commenting out until we find a better solution. + // It's not an easy problem. For now we'll live with the mis-attributions, as wall to wall comments are much less frequent + // than wall-to-wall posts. + +// if(($item['mid'] != $item['parent_mid']) && ($item['author_xchan'] != $item['owner_xchan']) && (($item['item_flags'] & (ITEM_WALL|ITEM_ORIGIN)) == (ITEM_WALL|ITEM_ORIGIN)) && (is_array($item['author'])) && $item['author']['xchan_url'] && $item['author']['xchan_name'] && $item['author']['xchan_photo_m']) { +// logger('bb2diaspora_itemwallwall: wall to wall comment',LOGGER_DEBUG); + // post will come across with the owner's identity. Throw a preamble onto the post to indicate the true author. +// $item['body'] = "\n\n" +// . '[img]' . $item['author']['xchan_photo_m'] . '[/img]' +// . '[url=' . $item['author']['xchan_url'] . ']' . $item['author']['xchan_name'] . '[/url]' . "\n\n" +// . $item['body']; +// } + // $item['author'] might cause a surprise further down the line if it wasn't expected to be here. if(! $author_exists) @@ -294,9 +313,9 @@ function bb2diaspora_itemwallwall(&$item) { } -function bb2diaspora_itembody($item) { +function bb2diaspora_itembody($item,$force_update = false) { - if($item['diaspora_meta']) { + if(($item['diaspora_meta']) && (! $force_update)) { $diaspora_meta = json_decode($item['diaspora_meta'],true); if($diaspora_meta) { if(array_key_exists('iv',$diaspora_meta)) { @@ -321,8 +340,13 @@ function bb2diaspora_itembody($item) { if(array_key_exists('item_flags',$item) && ($item['item_flags'] & ITEM_OBSCURED)) { $key = get_config('system','prvkey'); - $newitem['title'] = (($item['title']) ? crypto_unencapsulate(json_decode($item['title'],true),$key) : ''); - $newitem['body'] = (($item['body']) ? crypto_unencapsulate(json_decode($item['body'],true),$key) : ''); + $b = json_decode($item['body'],true); + // if called from diaspora_process_outbound, this decoding has already been done. + // Everything else that calls us will not yet be decoded. + if($b && is_array($b) && array_key_exists('iv',$b)) { + $newitem['title'] = (($item['title']) ? crypto_unencapsulate(json_decode($item['title'],true),$key) : ''); + $newitem['body'] = (($item['body']) ? crypto_unencapsulate(json_decode($item['body'],true),$key) : ''); + } } bb2diaspora_itemwallwall($newitem); diff --git a/include/conversation.php b/include/conversation.php index 92ba18d13..a531de9ba 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -649,7 +649,10 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional', $ $likebuttons = false; $shareable = false; - $verified = (($item['item_flags'] & ITEM_VERIFIED) ? t('Message is verified') : ''); + $verified = (($item['item_flags'] & ITEM_VERIFIED) ? t('Message signature validated') : ''); + $forged = ((($item['sig']) && (! ($item['item_flags'] & ITEM_VERIFIED))) ? t('Message signature incorrect') : ''); + + $unverified = ''; @@ -682,6 +685,7 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional', $ 'mentions' => $mentions, 'verified' => $verified, 'unverified' => $unverified, + 'forged' => $forged, 'txt_cats' => t('Categories:'), 'txt_folders' => t('Filed under:'), 'has_cats' => ((count($categories)) ? 'true' : ''), @@ -823,8 +827,8 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional', $ $threads = null; } - if($page_mode === 'preview') - logger('preview: ' . print_r($threads,true)); +// if($page_mode === 'preview') +// logger('preview: ' . print_r($threads,true)); // Do not un-comment if smarty3 is in use // logger('page_template: ' . $page_template); @@ -1547,16 +1551,18 @@ function profile_tabs($a, $is_owner=False, $nickname=Null){ ); } - require_once('include/chat.php'); - $has_chats = chatroom_list_count($uid); - if ($has_chats) { - $tabs[] = array( - 'label' => t('Chatrooms'), - 'url' => $a->get_baseurl() . '/chat/' . $nickname, - 'sel' => ((argv(0) == 'chat') ? 'active' : '' ), - 'title' => t('Chatrooms'), - 'id' => 'chat-tab', - ); + if($p['chat']) { + require_once('include/chat.php'); + $has_chats = chatroom_list_count($uid); + if ($has_chats) { + $tabs[] = array( + 'label' => t('Chatrooms'), + 'url' => $a->get_baseurl() . '/chat/' . $nickname, + 'sel' => ((argv(0) == 'chat') ? 'active' : '' ), + 'title' => t('Chatrooms'), + 'id' => 'chat-tab', + ); + } } require_once('include/menu.php'); diff --git a/include/datetime.php b/include/datetime.php index 84ab1e2fa..00bac8ad1 100644 --- a/include/datetime.php +++ b/include/datetime.php @@ -130,135 +130,101 @@ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d function dob($dob) { list($year,$month,$day) = sscanf($dob,'%4d-%2d-%2d'); - $y = datetime_convert('UTC',date_default_timezone_get(),'now','Y'); $f = get_config('system','birthday_input_format'); if(! $f) $f = 'ymd'; - $o = datesel($f,'',1920,$y,true,$year,$month,$day); - return $o; -} - -function datesel_format($f) { - - $o = ''; + if ($dob && $dob != '0000-00-00') + $o = datesel($f,mktime(0,0,0,0,0,1900),mktime(),mktime(0,0,0,$month,$day,$year),'dob'); + else + $o = datesel($f,mktime(0,0,0,0,0,1900),mktime(),false,'dob'); - if(strlen($f)) { - for($x = 0; $x < strlen($f); $x ++) { - switch($f[$x]) { - case 'y': - if(strlen($o)) - $o .= '-'; - $o .= t('year'); - break; - case 'm': - if(strlen($o)) - $o .= '-'; - $o .= t('month'); - break; - case 'd': - if(strlen($o)) - $o .= '-'; - $o .= t('day'); - break; - default: - break; - } - } - } return $o; } -// returns a date selector. -// $f = format string, e.g. 'ymd' or 'mdy' -// $pre = prefix (if needed) for HTML name and class fields -// $ymin = first year shown in selector dropdown -// $ymax = last year shown in selector dropdown -// $allow_blank = allow an empty response on any field -// $y = already selected year -// $m = already selected month -// $d = already selected day - +/** + * returns a date selector + * @param $format + * format string, e.g. 'ymd' or 'mdy'. Not currently supported + * @param $min + * unix timestamp of minimum date + * @param $max + * unix timestap of maximum date + * @param $default + * unix timestamp of default date + * @param $id + * id and name of datetimepicker (defaults to "datetimepicker") + */ +function datesel($format, $min, $max, $default,$id = 'datepicker') { + return datetimesel($format,$min,$max,$default,$id,true,false); +} -function datesel($f,$pre,$ymin,$ymax,$allow_blank,$y,$m,$d) { +/** + * returns a date selector + * @param $format + * format string, e.g. 'ymd' or 'mdy'. Not currently supported + * @param $h + * already selected hour + * @param $m + * already selected minute + * @param $id + * id and name of datetimepicker (defaults to "timepicker") + */ +function timesel($format,$h,$m,$id='timepicker') { + return datetimesel($format,mktime(),mktime(),mktime($h,$m),$id,false,true); +} +/** + * returns a datetime selector + * @param $format + * format string, e.g. 'ymd' or 'mdy'. Not currently supported + * @param $min + * unix timestamp of minimum date + * @param $max + * unix timestap of maximum date + * @param $default + * unix timestamp of default date + * @param $id + * id and name of datetimepicker (defaults to "datetimepicker") + * @param $pickdate + * true to show date picker (default) + * @param $picktime + * true to show time picker (default) + * @param $minfrom + * set minimum date from picker with id $minfrom (none by default) + * @param $maxfrom + * set maximum date from picker with id $maxfrom (none by default) + */ +function datetimesel($format, $min, $max, $default, $id = 'datetimepicker', $pickdate = true, $picktime = true, $minfrom = '', $maxfrom = '') { $o = ''; - if(strlen($f)) { - for($z = 0; $z < strlen($f); $z ++) { - if($f[$z] === 'y') { - - $o .= "<select name=\"{$pre}year\" class=\"{$pre}year\" size=\"1\">"; - if($allow_blank) { - $sel = (($y == '0000') ? " selected=\"selected\" " : ""); - $o .= "<option value=\"0000\" $sel ></option>"; - } - - if($ymax > $ymin) { - for($x = $ymax; $x >= $ymin; $x --) { - $sel = (($x == $y) ? " selected=\"selected\" " : ""); - $o .= "<option value=\"$x\" $sel>$x</option>"; - } - } - else { - for($x = $ymax; $x <= $ymin; $x ++) { - $sel = (($x == $y) ? " selected=\"selected\" " : ""); - $o .= "<option value=\"$x\" $sel>$x</option>"; - } - } - } - elseif($f[$z] == 'm') { - - $o .= "</select> <select name=\"{$pre}month\" class=\"{$pre}month\" size=\"1\">"; - for($x = (($allow_blank) ? 0 : 1); $x <= 12; $x ++) { - $sel = (($x == $m) ? " selected=\"selected\" " : ""); - $y = (($x) ? $x : ''); - $o .= "<option value=\"$x\" $sel>$y</option>"; - } - } - elseif($f[$z] == 'd') { - - $o .= "</select> <select name=\"{$pre}day\" class=\"{$pre}day\" size=\"1\">"; - for($x = (($allow_blank) ? 0 : 1); $x <= 31; $x ++) { - $sel = (($x == $d) ? " selected=\"selected\" " : ""); - $y = (($x) ? $x : ''); - $o .= "<option value=\"$x\" $sel>$y</option>"; - } - } - } - } + $dateformat = ''; + if($pickdate) $dateformat .= 'YYYY-MM-DD'; + if($pickdate && $picktime) $dateformat .= ' '; + if($picktime) $dateformat .= 'HH:mm'; - $o .= "</select>"; - return $o; -} + $mindate = $min ? "new Date($min*1000)" : ''; + $maxdate = $max ? "new Date($max*1000)" : ''; + + $defaultDate = $default ? ", defaultDate: new Date($default*1000)" : ''; + $pickers = ''; + if(!$pickdate) $pickers .= 'pickDate: false,'; + if(!$picktime) $pickers .= 'pickTime: false,'; -function timesel($pre,$h,$m) { + $extra_js = ''; + if($minfrom != '') + $extra_js .= "\$('#$minfrom').on('dp.change',function (e) { \$('#$id').data('DateTimePicker').setMinDate(e.date); });"; - $o = ''; - $o .= "<select name=\"{$pre}hour\" class=\"{$pre}hour\" size=\"1\">"; - for($x = 0; $x < 24; $x ++) { - $sel = (($x == $h) ? " selected=\"selected\" " : ""); - $o .= "<option value=\"$x\" $sel>$x</option>"; - } - $o .= "</select> : <select name=\"{$pre}minute\" class=\"{$pre}minute\" size=\"1\">"; - for($x = 0; $x < 60; $x ++) { - $sel = (($x == $m) ? " selected=\"selected\" " : ""); - $o .= "<option value=\"$x\" $sel>$x</option>"; - } + if($maxfrom != '') + $extra_js .= "\$('#$maxfrom').on('dp.change',function (e) { \$('#$id').data('DateTimePicker').setMaxDate(e.date); });"; - $o .= "</select>"; + $o .= "<div class='date' id='$id'><input type='text' placeholder='$dateformat' name='$id'/></div>"; + $o .= "<script type='text/javascript'>\$(function () {\$('#$id').datetimepicker({sideBySide: true, $pickers minDate: $mindate, maxDate: $maxdate, format: '$dateformat', useCurrent: false $defaultDate}); $extra_js});</script>"; return $o; } - - - - - - - // implements "3 seconds ago" etc. // based on $posted_date, (UTC). // Results relative to current timezone @@ -516,4 +482,4 @@ function update_birthdays() { } } } -}
\ No newline at end of file +} diff --git a/include/deliver.php b/include/deliver.php index f4fae6061..8f6ba543d 100644 --- a/include/deliver.php +++ b/include/deliver.php @@ -38,15 +38,30 @@ function deliver_run($argv, $argc) { continue; } - if($r[0]['outq_posturl'] === z_root() . '/post') { + $notify = json_decode($r[0]['outq_notify'],true); + + $sendtoweb = false; + if(array_key_exists('iv',$notify) && (! $r[0]['outq_msg'])) + $sendtoweb = true; + + if(($r[0]['outq_posturl'] === z_root() . '/post') && (! $sendtoweb)) { logger('deliver: local delivery', LOGGER_DEBUG); // local delivery // we should probably batch these and save a few delivery processes // If there is no outq_msg, this is a refresh_all message which does not require local handling + // also send 'request' packets to the webservice so it can decode the packet if($r[0]['outq_msg']) { - $msg = array('body' => json_encode(array('pickup' => array(array('notify' => json_decode($r[0]['outq_notify'],true),'message' => json_decode($r[0]['outq_msg'],true)))))); - - zot_import($msg,z_root()); + $m = json_decode($r[0]['outq_msg'],true); + if(array_key_exists('message_list',$m)) { + foreach($m['message_list'] as $mm) { + $msg = array('body' => json_encode(array('pickup' => array(array('notify' => $notify,'message' => $mm))))); + zot_import($msg,z_root()); + } + } + else { + $msg = array('body' => json_encode(array('pickup' => array(array('notify' => $notify,'message' => $m))))); + zot_import($msg,z_root()); + } $r = q("delete from outq where outq_hash = '%s' limit 1", dbesc($argv[$x]) ); diff --git a/include/diaspora.php b/include/diaspora.php index fb321a813..c6d4b7423 100755 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -927,9 +927,14 @@ function get_diaspora_reshare_xml($url,$recurse = 0) { // see if it's a reshare of a reshare - if($source_xml->root_diaspora_id && $source_xml->root_guid && $recurse < 15) { - $orig_author = notags(unxmlify($source_xml->root_diaspora_id)); - $orig_guid = notags(unxmlify($source_xml->root_guid)); + if($source_xml->post->reshare) + $xml = $source_xml->post->reshare; + else + return false; + + if($xml->root_diaspora_id && $xml->root_guid && $recurse < 15) { + $orig_author = notags(unxmlify($xml->root_diaspora_id)); + $orig_guid = notags(unxmlify($xml->root_guid)); $source_url = 'https://' . substr($orig_author,strpos($orig_author,'@')+1) . '/p/' . $orig_guid . '.xml'; $y = get_diaspora_reshare_xml($source_url,$recurse+1); if($y) @@ -2257,7 +2262,7 @@ function diaspora_send_status($item,$owner,$contact,$public_batch = false) { $images = array(); $title = $item['title']; - $body = bb2diaspora_itembody($item); + $body = bb2diaspora_itembody($item,true); /* // We're trying to match Diaspora's split message/photo protocol but diff --git a/include/event.php b/include/event.php index 1fe6e6f7f..03ecaa0a7 100644 --- a/include/event.php +++ b/include/event.php @@ -400,8 +400,7 @@ function event_store_item($arr,$event) { } else { - $z = q("select * from channel where channel_hash = '%s' and channel_id = %d limit 1", - dbesc($event['event_xchan']), + $z = q("select * from channel where channel_id = %d limit 1", intval($arr['uid']) ); @@ -413,7 +412,7 @@ function event_store_item($arr,$event) { $item_arr['id'] = $item['id']; } else { - $wall = (($z) ? true : false); + $wall = (($z[0]['channel_hash'] == $event['event_xchan']) ? true : false); $item_flags = ITEM_THREAD_TOP; if($wall) { @@ -455,7 +454,14 @@ function event_store_item($arr,$event) { $item_arr['body'] = $prefix . format_event_bbcode($arr); - $item_arr['plink'] = z_root() . '/channel/' . $z[0]['channel_address'] . '/?f=&mid=' . $item_arr['mid']; + // if it's local send the permalink to the channel page. + // otherwise we'll fallback to /display/$message_id + + if($wall) + $item_arr['plink'] = z_root() . '/channel/' . $z[0]['channel_address'] . '/?f=&mid=' . $item_arr['mid']; + else + $item_arr['plink'] = z_root() . '/display/' . $item_arr['mid']; + $x = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($arr['event_xchan']) diff --git a/include/follow.php b/include/follow.php index c8bd3c500..20fd7f5fc 100644 --- a/include/follow.php +++ b/include/follow.php @@ -13,6 +13,8 @@ require_once('include/zot.php'); function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) { + + $result = array('success' => false,'message' => ''); $a = get_app(); @@ -64,7 +66,11 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) if($is_red && $j) { - $my_perms = PERMS_W_STREAM|PERMS_W_MAIL; + + // fixme - we need to be able to define these somewhere for the custom role + $my_perms = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_PHOTOS|PERMS_R_ABOOK + |PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT + |PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE; $role = get_pconfig($uid,'system','permissions_role'); if($role) { diff --git a/include/html2bbcode.php b/include/html2bbcode.php index df430e6c7..9ffc85a82 100644 --- a/include/html2bbcode.php +++ b/include/html2bbcode.php @@ -16,7 +16,7 @@ function node2bbcode(&$doc, $oldnode, $attributes, $startbb, $endbb) function node2bbcodesub(&$doc, $oldnode, $attributes, $startbb, $endbb) { - $savestart = str_replace('$', '%', $startbb); + $savestart = str_replace('$', '\x01', $startbb); $replace = false; $xpath = new DomXPath($doc); @@ -37,7 +37,7 @@ function node2bbcodesub(&$doc, $oldnode, $attributes, $startbb, $endbb) foreach ($attributes as $attribute => $value) { - $startbb = str_replace('%'.++$i, '$1', $startbb); + $startbb = str_replace('\x01'.++$i, '$1', $startbb); if (strpos('*'.$startbb, '$1') > 0) { @@ -283,8 +283,9 @@ function html2bbcode($message) array('[b]', '[/b]', '[i]', '[/i]'), $message); // Handling Yahoo style of mails - $message = str_replace('[hr][b]From:[/b]', '[quote][b]From:[/b]', $message); + // $message = str_replace('[hr][b]From:[/b]', '[quote][b]From:[/b]', $message); + $message = htmlspecialchars($message,ENT_COMPAT,'UTF-8',false); return(trim($message)); } diff --git a/include/hubloc.php b/include/hubloc.php index fded434d2..0a1b51331 100644 --- a/include/hubloc.php +++ b/include/hubloc.php @@ -113,4 +113,149 @@ function remove_obsolete_hublocs() { } -
\ No newline at end of file +// This actually changes other structures to match the given (presumably current) hubloc primary selection + +function hubloc_change_primary($hubloc) { + + if(! is_array($hubloc)) { + logger('no hubloc'); + return false; + } + if(! ($hubloc['hubloc_flags'] & HUBLOC_FLAGS_PRIMARY)) { + logger('not primary: ' . $hubloc['hubloc_url']); + return false; + } + + logger('setting primary: ' . $hubloc['hubloc_url']); + + // See if there's a local channel + + $r = q("select channel_id, channel_primary from channel where channel_hash = '%s' limit 1", + dbesc($hubloc['hubloc_hash']) + ); + if(($r) && (! $r[0]['channel_primary'])) { + q("update channel set channel_primary = 1 where channel_id = %d limit 1", + intval($r[0]['channel_id']) + ); + } + + // do we even have an xchan for this hubloc and if so is it already set as primary? + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($hubloc['hubloc_hash']) + ); + if(! $r) { + logger('xchan not found'); + return false; + } + if($r[0]['xchan_addr'] === $hubloc['hubloc_addr']) { + logger('xchan already changed'); + return false; + } + + $url = $hubloc['hubloc_url']; + $lwebbie = substr($hubloc['hubloc_addr'],0,strpos($hubloc['hubloc_addr'],'@')); + + $r = q("update xchan set xchan_addr = '%s', xchan_url = '%s', xchan_follow = '%s', xchan_connurl = '%s' where xchan_hash = '%s' limit 1", + dbesc($hubloc['hubloc_addr']), + dbesc($url . '/channel/' . $lwebbie), + dbesc($url . '/follow?f=&url=%s'), + dbesc($url . '/poco/' . $lwebbie), + dbesc($hubloc['hubloc_hash']) + ); + if(! $r) + logger('xchan_update failed.'); + + logger('primary hubloc changed.' . print_r($hubloc,true),LOGGER_DEBUG); + return true; + +} + + +function xchan_store($arr) { + + if(! $arr['hash']) + $arr['hash'] = $arr['guid']; + if(! $arr['hash']) + return false; + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($arr['hash']) + ); + if($r) + return true; + + if(! $arr['network']) + $arr['network'] = 'unknown'; + if(! $arr['name']) + $arr['name'] = 'unknown'; + if(! $arr['url']) + $arr['url'] = z_root(); + if(! $arr['photo']) + $arr['photo'] = get_default_profile_photo(); + + $r = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_addr, xchan_url, xchan_connurl, xchan_follow, xchan_connpage, xchan_name, xchan_network, xchan_instance_url, xchan_flags, xchan_name_date ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s','%s','%s','%s',%d,'%s') ", + dbesc($arr['hash']), + dbesc($arr['guid']), + dbesc($arr['guid_sig']), + dbesc($arr['pubkey']), + dbesc($arr['address']), + dbesc($arr['url']), + dbesc($arr['connurl']), + dbesc($arr['follow']), + dbesc($arr['connpage']), + dbesc($arr['name']), + dbesc($arr['network']), + dbesc($arr['instance_url']), + intval($arr['flags']), + dbesc(datetime_convert()) + ); + if(! $r) + return $r; + + $photos = import_profile_photo($arr['photo'],$arr['hash']); + $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_hash = '%s' limit 1", + dbesc(datetime_convert()), + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($arr['hash']) + ); + return $r; + +} + + +function xchan_fetch($arr) { + + $key = ''; + if($arr['hash']) { + $key = 'xchan_hash'; + $v = $arr['hash']; + } + elseif($arr['guid']) { + $key = 'xchan_guid'; + $v = $arr['guid']; + } + elseif($arr['address']) { + $key = 'xchan_addr'; + $v = $arr['address']; + } + + if(! $key) + return false; + + $r = q("select * from xchan where $key = '$v'"); + if(! $r) + return false; + + $ret = array(); + foreach($r as $k => $v) { + if($k === 'xchan_addr') + $ret['address'] = $v; + else + $ret[str_replace('xchan_','',$k)] = $v; + } + return $ret; +}
\ No newline at end of file diff --git a/include/identity.php b/include/identity.php index 4417f4028..9a574ea65 100644 --- a/include/identity.php +++ b/include/identity.php @@ -176,6 +176,7 @@ function create_identity($arr) { // save this for auto_friending $total_identities = $ret['total_identities']; + $nick = mb_strtolower(trim($arr['nickname'])); if(! $nick) { $ret['message'] = t('Nickname is required.'); @@ -291,8 +292,8 @@ function create_identity($arr) { // Create a verified hub location pointing to this site. $r = q("insert into hubloc ( hubloc_guid, hubloc_guid_sig, hubloc_hash, hubloc_addr, hubloc_flags, - hubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey ) - values ( '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s' )", + hubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey, hubloc_network ) + values ( '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s' )", dbesc($guid), dbesc($sig), dbesc($hash), @@ -302,7 +303,8 @@ function create_identity($arr) { dbesc(base64url_encode(rsa_sign(z_root(),$ret['channel']['channel_prvkey']))), dbesc(get_app()->get_hostname()), dbesc(z_root() . '/post'), - dbesc(get_config('system','pubkey')) + dbesc(get_config('system','pubkey')), + dbesc('zot') ); if(! $r) logger('create_identity: Unable to store hub location'); @@ -388,7 +390,8 @@ function create_identity($arr) { dbesc( t('Friends') ) ); if($r) { - q("update channel set channel_allow_gid = '%s' where channel_id = %d limit 1", + q("update channel set channel_default_group = '%s', channel_allow_gid = '%s' where channel_id = %d limit 1", + dbesc($r[0]['hash']), dbesc('<' . $r[0]['hash'] . '>'), intval($newuid) ); @@ -402,6 +405,7 @@ function create_identity($arr) { $accts = get_config('system','auto_follow'); if(($accts) && (! $total_identities)) { + require_once('include/follow.php'); if(! is_array($accts)) $accts = array($accts); foreach($accts as $acct) { diff --git a/include/items.php b/include/items.php index c4ae948b8..d173e1a98 100755 --- a/include/items.php +++ b/include/items.php @@ -98,6 +98,7 @@ function collect_recipients($item,&$private_envelope) { } } + // This is a somewhat expensive operation but important. // Don't send this item to anybody who isn't allowed to see it @@ -213,6 +214,7 @@ function can_comment_on_post($observer_xchan,$item) { break; case 'any connections': case 'contacts': + case 'authenticated': case '': if(array_key_exists('owner',$item)) { if(($item['owner']['abook_xchan']) && ($item['owner']['abook_their_perms'] & PERMS_W_COMMENT)) @@ -2516,7 +2518,7 @@ function item_store_update($arr,$allow_exec = false) { return $ret; } -function store_diaspora_comment_sig($datarray, $channel, $parent_item, $post_id) { +function store_diaspora_comment_sig($datarray, $channel, $parent_item, $post_id, $walltowall = false) { // We won't be able to sign Diaspora comments for authenticated visitors // - we don't have their private key @@ -2524,9 +2526,18 @@ function store_diaspora_comment_sig($datarray, $channel, $parent_item, $post_id) // since Diaspora doesn't handle edits we can only do this for the original text and not update it. require_once('include/bb2diaspora.php'); - $signed_body = bb2diaspora_itembody($datarray); + $signed_body = bb2diaspora_itembody($datarray,$walltowall); + + if($walltowall) { + logger('wall to wall comment',LOGGER_DEBUG); + // post will come across with the owner's identity. Throw a preamble onto the post to indicate the true author. + $signed_body = "\n\n" + . '![' . $datarray['author']['xchan_name'] . '](' . $datarray['author']['xchan_photo_m'] . ')' + . '[' . $datarray['author']['xchan_name'] . '](' . $datarray['author']['xchan_url'] . ')' . "\n\n" + . $signed_body; + } - logger('mod_item: storing diaspora comment signature',LOGGER_DEBUG); + logger('storing diaspora comment signature',LOGGER_DEBUG); $diaspora_handle = $channel['channel_address'] . '@' . get_app()->get_hostname(); @@ -3977,7 +3988,12 @@ function drop_item($id,$interactive = true,$stage = DROPITEM_NORMAL) { // send the notification upstream/downstream as the case may be // only send notifications to others if this is the owner's wall item. - if(($item['item_flags'] & ITEM_WALL) && ($stage != DROPITEM_PHASE2)) + // This isn't optimal. We somehow need to pass to this function whether or not + // to call the notifier, or we need to call the notifier from the calling function. + // We'll rely on the undocumented behaviour that DROPITEM_PHASE1 is (hopefully) only + // set if we know we're going to send delete notifications out to others. + + if((($item['item_flags'] & ITEM_WALL) && ($stage != DROPITEM_PHASE2)) || ($stage == DROPITEM_PHASE1)) proc_run('php','include/notifier.php','drop',$notify_id); goaway($a->get_baseurl() . '/' . $_SESSION['return_url']); @@ -4125,7 +4141,7 @@ function list_post_dates($uid,$wall) { if(intval(substr($dnow,8)) > 28) $dnow = substr($dnow,0,8) . '28'; if(intval(substr($dthen,8)) > 28) - $dnow = substr($dthen,0,8) . '28'; + $dthen = substr($dthen,0,8) . '28'; $ret = array(); // Starting with the current month, get the first and last days of every @@ -4159,7 +4175,7 @@ function posted_dates($uid,$wall) { if(intval(substr($dnow,8)) > 28) $dnow = substr($dnow,0,8) . '28'; if(intval(substr($dthen,8)) > 28) - $dnow = substr($dthen,0,8) . '28'; + $dthen = substr($dthen,0,8) . '28'; $ret = array(); // Starting with the current month, get the first and last days of every @@ -4233,14 +4249,24 @@ function fetch_post_tags($items,$link = false) { -function zot_feed($uid,$observer_xchan,$mindate) { +function zot_feed($uid,$observer_xchan,$arr) { $result = array(); - $mindate = datetime_convert('UTC','UTC',$mindate); + $mindate = null; + $message_id = null; + + if(array_key_exists('mindate',$arr)) { + $mindate = datetime_convert('UTC','UTC',$arr['mindate']); + } + + if(array_key_exists('message_id',$arr)) { + $message_id = $arr['message_id']; + } + + if(! $mindate) $mindate = NULL_DATE; - $mindate = dbesc($mindate); logger('zot_feed: ' . $uid); @@ -4262,6 +4288,11 @@ function zot_feed($uid,$observer_xchan,$mindate) { else $limit = " limit 0, 50 "; + if($message_id) { + $sql_extra .= " and mid = '" . dbesc($message_id) . "' "; + $limit = ''; + } + $items = array(); if(is_sys_channel($uid)) { diff --git a/include/nav.php b/include/nav.php index 4f7d609aa..f1f89db20 100644 --- a/include/nav.php +++ b/include/nav.php @@ -84,6 +84,7 @@ EOT; $nav['usermenu']=array(); $userinfo = null; + $nav['loginmenu']=array(); if(local_user()) { @@ -120,8 +121,9 @@ EOT; $nav['usermenu'][] = Array('webpages/' . $channel['channel_address'],t('Webpages'),"",t('Your webpages')); } else { - if(! get_account_id()) - $nav['login'] = Array('login',t('Login'), ($a->module == 'login'?'selected':''), t('Sign in')); + if(! get_account_id()) { + $nav['loginmenu'][] = Array('login',t('Login'),'',t('Sign in')); + } else $nav['alogout'] = Array('logout',t('Logout'), "", t('End this session')); @@ -136,14 +138,11 @@ EOT; } if($observer) { - $nav['locked'] = true; $nav['lock'] = array('logout','','lock', sprintf( t('%s - click to logout'), $observer['xchan_addr'])); } else { - $nav['locked'] = false; - $nav['lock'] = array('rmagic','','unlock', - t('Click to authenticate to your home hub')); + $nav['loginmenu'][] = Array('rmagic',t('Remote authentication'),'',t('Click to authenticate to your home hub')); } /** diff --git a/include/network.php b/include/network.php index 7286f0b12..25ed615c6 100644 --- a/include/network.php +++ b/include/network.php @@ -402,7 +402,7 @@ function validate_email($addr) { return false; $h = substr($addr,strpos($addr,'@') + 1); - if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX) || filter_var($h['host'], FILTER_VALIDATE_IP) )) { + if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX) || filter_var($h, FILTER_VALIDATE_IP) )) { return true; } return false; diff --git a/include/notifier.php b/include/notifier.php index 79a6886ad..c193db116 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -58,6 +58,7 @@ require_once('include/html2plain.php'); * expire channel_id * relay item_id (item was relayed to owner, we will deliver it as owner) * location channel_id + * request channel_id xchan_hash message_id * */ @@ -142,6 +143,7 @@ function notifier_run($argv, $argc){ $expire = false; + $request = false; $mail = false; $fsuggest = false; $top_level = false; @@ -175,6 +177,22 @@ function notifier_run($argv, $argc){ $channel = $s[0]; } + elseif($cmd === 'request') { + $channel_id = $item_id; + $xchan = $argv[3]; + $request_message_id = $argv[4]; + + $s = q("select * from channel where channel_id = %d limit 1", + intval($channel_id) + ); + if($s) + $channel = $s[0]; + + $private = true; + $recipients[] = $xchan; + $packet_type = 'request'; + $normal_mode = false; + } elseif($cmd === 'expire') { // FIXME @@ -550,6 +568,7 @@ function notifier_run($argv, $argc){ 'mail' => $mail, 'location' => $location, 'fsuggest' => $fsuggest, + 'request' => $request, 'normal_mode' => $normal_mode, 'packet_type' => $packet_type, 'walltowall' => $walltowall @@ -579,6 +598,21 @@ function notifier_run($argv, $argc){ dbesc('') ); } + elseif($packet_type === 'request') { + $n = zot_build_packet($channel,'request',$env_recips,$hub['hubloc_sitekey'],$hash,array('message_id' => $request_message_id)); + q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) values ( '%s', %d, %d, '%s', '%s', %d, '%s', '%s', '%s', '%s' )", + dbesc($hash), + intval($channel['channel_account_id']), + intval($channel['channel_id']), + dbesc('zot'), + dbesc($hub['hubloc_callback']), + intval(1), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($n), + dbesc('') + ); + } else { $n = zot_build_packet($channel,'notify',$env_recips,(($private) ? $hub['hubloc_sitekey'] : null),$hash); q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) values ( '%s', %d, %d, '%s', '%s', %d, '%s', '%s', '%s', '%s' )", diff --git a/include/onepoll.php b/include/onepoll.php index 98d52db93..7a81282b3 100644 --- a/include/onepoll.php +++ b/include/onepoll.php @@ -15,12 +15,6 @@ function onepoll_run($argv, $argc){ logger('onepoll: start'); - $manual_id = 0; - $generation = 0; - - $force = false; - $restart = false; - if(($argc > 1) && (intval($argv[1]))) $contact_id = intval($argv[1]); @@ -28,7 +22,7 @@ function onepoll_run($argv, $argc){ logger('onepoll: no contact'); return; } - + $d = datetime_convert(); $contacts = q("SELECT abook.*, xchan.*, account.* @@ -90,7 +84,8 @@ function onepoll_run($argv, $argc){ $x = zot_refresh($contact,$importer); $responded = false; - $updated = datetime_convert(); + $updated = datetime_convert(); + $connected = datetime_convert(); if(! $x) { // mark for death by not updating abook_connected, this is caught in include/poller.php q("update abook set abook_updated = '%s' where abook_id = %d limit 1", @@ -101,7 +96,7 @@ function onepoll_run($argv, $argc){ else { q("update abook set abook_updated = '%s', abook_connected = '%s' where abook_id = %d limit 1", dbesc($updated), - dbesc($updated), + dbesc($connected), intval($contact['abook_id']) ); $responded = true; @@ -120,7 +115,9 @@ function onepoll_run($argv, $argc){ if($fetch_feed) { $feedurl = str_replace('/poco/','/zotfeed/',$contact['xchan_connurl']); - $x = z_fetch_url($feedurl . '?f=&mindate=' . urlencode($last_update)); + $feedurl .= '?f=&mindate=' . urlencode($last_update); + + $x = z_fetch_url($feedurl); logger('feed_update: ' . print_r($x,true), LOGGER_DATA); @@ -143,8 +140,8 @@ function onepoll_run($argv, $argc){ } } - // fetch some items - // set last updated timestamp + + // update the poco details for this connection if($contact['xchan_connurl']) { $r = q("SELECT xlink_id from xlink diff --git a/include/permissions.php b/include/permissions.php index 61ac8aea3..186ba32d8 100644 --- a/include/permissions.php +++ b/include/permissions.php @@ -460,6 +460,7 @@ function get_role_perms($role) { $ret['channel_w_chat'] = PERMS_CONTACTS; $ret['channel_a_delegate'] = 0; $ret['channel_r_storage'] = PERMS_PUBLIC; + $ret['channel_w_storage'] = 0; $ret['channel_r_pages'] = PERMS_PUBLIC; $ret['channel_w_pages'] = 0; $ret['channel_a_republish'] = PERMS_SPECIFIC; @@ -492,6 +493,7 @@ function get_role_perms($role) { $ret['channel_w_chat'] = PERMS_CONTACTS; $ret['channel_a_delegate'] = 0; $ret['channel_r_storage'] = PERMS_PUBLIC; + $ret['channel_w_storage'] = 0; $ret['channel_r_pages'] = PERMS_PUBLIC; $ret['channel_w_pages'] = 0; $ret['channel_a_republish'] = PERMS_SPECIFIC; @@ -524,6 +526,7 @@ function get_role_perms($role) { $ret['channel_w_chat'] = PERMS_SPECIFIC; $ret['channel_a_delegate'] = 0; $ret['channel_r_storage'] = PERMS_PUBLIC; + $ret['channel_w_storage'] = 0; $ret['channel_r_pages'] = PERMS_PUBLIC; $ret['channel_w_pages'] = 0; $ret['channel_a_republish'] = PERMS_SPECIFIC; @@ -555,6 +558,7 @@ function get_role_perms($role) { $ret['channel_w_chat'] = PERMS_CONTACTS; $ret['channel_a_delegate'] = 0; $ret['channel_r_storage'] = PERMS_PUBLIC; + $ret['channel_w_storage'] = 0; $ret['channel_r_pages'] = PERMS_PUBLIC; $ret['channel_w_pages'] = 0; $ret['channel_a_republish'] = PERMS_SPECIFIC; @@ -586,6 +590,7 @@ function get_role_perms($role) { $ret['channel_w_chat'] = PERMS_CONTACTS; $ret['channel_a_delegate'] = 0; $ret['channel_r_storage'] = PERMS_PUBLIC; + $ret['channel_w_storage'] = 0; $ret['channel_r_pages'] = PERMS_PUBLIC; $ret['channel_w_pages'] = 0; $ret['channel_a_republish'] = PERMS_SPECIFIC; @@ -618,6 +623,7 @@ function get_role_perms($role) { $ret['channel_w_chat'] = PERMS_SPECIFIC; $ret['channel_a_delegate'] = 0; $ret['channel_r_storage'] = PERMS_CONTACTS; + $ret['channel_w_storage'] = 0; $ret['channel_r_pages'] = PERMS_CONTACTS; $ret['channel_w_pages'] = 0; $ret['channel_a_republish'] = PERMS_SPECIFIC; @@ -649,6 +655,7 @@ function get_role_perms($role) { $ret['channel_w_chat'] = PERMS_CONTACTS; $ret['channel_a_delegate'] = 0; $ret['channel_r_storage'] = PERMS_PUBLIC; + $ret['channel_w_storage'] = 0; $ret['channel_r_pages'] = PERMS_PUBLIC; $ret['channel_w_pages'] = 0; $ret['channel_a_republish'] = PERMS_NETWORK; @@ -681,6 +688,7 @@ function get_role_perms($role) { $ret['channel_w_chat'] = PERMS_CONTACTS; $ret['channel_a_delegate'] = 0; $ret['channel_r_storage'] = PERMS_PUBLIC; + $ret['channel_w_storage'] = 0; $ret['channel_r_pages'] = PERMS_PUBLIC; $ret['channel_w_pages'] = 0; $ret['channel_a_republish'] = PERMS_SPECIFIC; @@ -710,6 +718,7 @@ function get_role_perms($role) { $ret['channel_w_chat'] = 0; $ret['channel_a_delegate'] = 0; $ret['channel_r_storage'] = PERMS_PUBLIC; + $ret['channel_w_storage'] = 0; $ret['channel_r_pages'] = PERMS_PUBLIC; $ret['channel_w_pages'] = 0; $ret['channel_a_republish'] = PERMS_SPECIFIC; @@ -717,6 +726,40 @@ function get_role_perms($role) { break; + case 'repository': + $ret['perms_auto'] = true; + $ret['default_collection'] = false; + $ret['directory_publish'] = true; + $ret['online'] = false; + $ret['perms_follow'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_PHOTOS|PERMS_R_ABOOK + |PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT + |PERMS_R_STORAGE|PERMS_W_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE|PERMS_W_TAGWALL; + $ret['perms_accept'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_PHOTOS|PERMS_R_ABOOK + |PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT + |PERMS_R_STORAGE|PERMS_W_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE|PERMS_W_TAGWALL; + $ret['channel_r_stream'] = PERMS_PUBLIC; + $ret['channel_r_profile'] = PERMS_PUBLIC; + $ret['channel_r_photos'] = PERMS_PUBLIC; + $ret['channel_r_abook'] = PERMS_PUBLIC; + $ret['channel_w_stream'] = PERMS_CONTACTS; + $ret['channel_w_wall'] = PERMS_CONTACTS; + $ret['channel_w_tagwall'] = PERMS_CONTACTS; + $ret['channel_w_comment'] = PERMS_CONTACTS; + $ret['channel_w_mail'] = PERMS_CONTACTS; + $ret['channel_w_photos'] = PERMS_CONTACTS; + $ret['channel_w_chat'] = PERMS_CONTACTS; + $ret['channel_a_delegate'] = 0; + $ret['channel_r_storage'] = PERMS_PUBLIC; + $ret['channel_w_storage'] = PERMS_CONTACTS; + $ret['channel_r_pages'] = PERMS_PUBLIC; + $ret['channel_w_pages'] = PERMS_CONTACTS; + $ret['channel_a_republish'] = PERMS_SPECIFIC; + $ret['channel_w_like'] = PERMS_NETWORK; + + break; + + + default: break; @@ -734,6 +777,10 @@ function get_role_perms($role) { function role_selector($current) { + + if(! $current) + $current = 'custom'; + $roles = array( 'social' => array( t('Social Networking'), array('social' => t('Mostly Public'), 'social_restricted' => t('Restricted'), 'social_private' => t('Private'))), @@ -741,8 +788,8 @@ function role_selector($current) { array('forum' => t('Mostly Public'), 'forum_restricted' => t('Restricted'), 'forum_private' => t('Private'))), 'feed' => array( t('Feed Republish'), array('feed' => t('Mostly Public'), 'feed_restricted' => t('Restricted'))), - 'soapbox' => array( t('Celebrity/Soapbox'), - array('soapbox' => t('Mostly Public'))), + 'special' => array( t('Special Purpose'), + array('soapbox' => t('Celebrity/Soapbox'), 'repository' => t('Group Repository'))), 'other' => array( t('Other'), array('custom' => t('Custom/Expert Mode')))); $o = '<select name="permissions_role" id="privacy-role-select">'; diff --git a/include/poller.php b/include/poller.php index 2febaeb32..f11618d37 100644 --- a/include/poller.php +++ b/include/poller.php @@ -25,6 +25,15 @@ function poller_run($argv, $argc){ if(! $interval) $interval = ((get_config('system','delivery_interval') === false) ? 3 : intval(get_config('system','delivery_interval'))); + // Check for a lockfile. If it exists, but is over an hour old, it's stale. Ignore it. + $lockfile = 'store/[data]/poller'; + if ((file_exists($lockfile)) && (filemtime($lockfile) > (time() - 3600))) { + logger("poller: Already running"); + return; + } + + // Create a lockfile. Needs two vars, but $x doesn't need to contain anything. + file_put_contents($lockfile, $x); logger('poller: start'); @@ -254,7 +263,7 @@ function poller_run($argv, $argc){ ); - $contacts = q("SELECT abook_id, abook_flags, abook_updated, abook_connected, abook_closeness, abook_channel + $contacts = q("SELECT abook_id, abook_flags, abook_updated, abook_connected, abook_closeness, abook_xchan, abook_channel FROM abook LEFT JOIN account on abook_account = account_id where 1 $sql_extra AND (( abook_flags & %d ) OR ( abook_flags = %d )) @@ -310,12 +319,17 @@ function poller_run($argv, $argc){ // He's dead, Jim if(strcmp(datetime_convert('UTC','UTC', 'now'),datetime_convert('UTC','UTC', $c . " + 30 day")) > 0) { - $r = q("update abook set abook_flags = (abook_flags | %d) where abook_id = %d limit 1", - intval(ABOOK_FLAG_ARCHIVED), - intval($contact['abook_id']) + $n = q("select xchan_network from xchan where xchan_hash = '%s' limit 1", + dbesc($contact['abook_xchan']) ); - $update = false; - continue; + if($n && $n[0]['xchan_network'] == 'zot') { + $r = q("update abook set abook_flags = (abook_flags | %d) where abook_id = %d limit 1", + intval(ABOOK_FLAG_ARCHIVED), + intval($contact['abook_id']) + ); + $update = false; + continue; + } } if($contact['abook_flags'] & ABOOK_FLAG_ARCHIVED) { @@ -370,7 +384,10 @@ function poller_run($argv, $argc){ } } } - + + //All done - clear the lockfile + @unlink($lockfile); + return; } diff --git a/include/reddav.php b/include/reddav.php index 5c93daf1f..c16e08e27 100644 --- a/include/reddav.php +++ b/include/reddav.php @@ -1,724 +1,31 @@ <?php /** * @file include/reddav.php - * @brief DAV related classes from SabreDAV for Red Matrix. + * @brief some DAV related functions for RedMatrix. * - * This file contains the classes from SabreDAV that got extended to adapt it - * for Red Matrix. + * This file contains some functions which did not fit into one of the RedDAV + * classes. * - * You find the original SabreDAV classes under @ref vendor/sabre/dav/. + * The extended SabreDAV classes you will find in the RedDAV namespace under + * @ref includes/RedDAV/. + * The original SabreDAV classes you can find under @ref vendor/sabre/dav/. * We need to use SabreDAV 1.8.x for PHP5.3 compatibility. SabreDAV >= 2.0 * requires PHP >= 5.4. * * @todo split up the classes into own files. + * + * @link http://github.com/friendica/red + * @license http://opensource.org/licenses/mit-license.php The MIT License (MIT) */ use Sabre\DAV; +use RedMatrix\RedDAV; require_once('vendor/autoload.php'); require_once('include/attach.php'); - -/** - * @brief RedDirectory class. - * - * A class that represents a directory. - * - * @extends \Sabre\DAV\Node - * @implements \Sabre\DAV\ICollection - * @implements \Sabre\DAV\IQuota - */ -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 - ); - } -} // class RedDirectory - - - -/** - * RedFile class. - * - */ -class RedFile extends DAV\Node implements DAV\IFile { - - private $data; - private $auth; - 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 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 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']); - } -} // class RedFile - +require_once('include/RedDAV/RedFile.php'); +require_once('include/RedDAV/RedDirectory.php'); +require_once('include/RedDAV/RedBasicAuth.php'); /** * @brief Returns an array with viewable channels. @@ -727,9 +34,10 @@ class RedFile extends DAV\Node implements DAV\IFile { * has <b>view_storage</b> perms. * * @todo Is there any reason why this is not inside RedDirectory class? + * @fixme function name looks like a class name, should we rename it? * - * @param $auth - * @return array containing RedDirectory objects + * @param RedBasicAuth &$auth + * @return array RedDirectory[] */ function RedChannelList(&$auth) { $ret = array(); @@ -742,9 +50,9 @@ function RedChannelList(&$auth) { if ($r) { foreach ($r as $rr) { if (perm_is_allowed($rr['channel_id'], $auth->observer, 'view_storage')) { - logger('RedChannelList: ' . '/cloud/' . $rr['channel_address'], LOGGER_DATA); + logger('found channel: /cloud/' . $rr['channel_address'], LOGGER_DATA); // @todo can't we drop '/cloud'? It gets stripped off anyway in RedDirectory - $ret[] = new RedDirectory('/cloud/' . $rr['channel_address'], $auth); + $ret[] = new RedDAV\RedDirectory('/cloud/' . $rr['channel_address'], $auth); } } } @@ -757,11 +65,15 @@ function RedChannelList(&$auth) { * * Array with all RedDirectory and RedFile DAV\Node items for the given path. * - * @todo Is there any reason why this is not inside RedDirectory class? Seems only to be used there and we could simplify it a bit there. + * @todo Is there any reason why this is not inside RedDirectory class? Seems + * only to be used there and we could simplify it a bit there. + * @fixme function name looks like a class name, should we rename it? * * @param string $file path to a directory - * @param &$auth - * @returns array DAV\INode[] + * @param RedBasicAuth &$auth + * @returns null|array \Sabre\DAV\INode[] + * @throw \Sabre\DAV\Exception\Forbidden + * @throw \Sabre\DAV\Exception\NotFound */ function RedCollectionData($file, &$auth) { $ret = array(); @@ -778,7 +90,7 @@ function RedCollectionData($file, &$auth) { $file = trim($file, '/'); $path_arr = explode('/', $file); - + if (! $path_arr) return null; @@ -840,7 +152,7 @@ function RedCollectionData($file, &$auth) { // This should no longer be needed since we just returned errors for paths not found if ($path !== '/' . $file) { - logger("RedCollectionData: Path mismatch: $path !== /$file"); + logger("Path mismatch: $path !== /$file"); return NULL; } @@ -850,13 +162,12 @@ function RedCollectionData($file, &$auth) { ); foreach ($r as $rr) { - logger('RedCollectionData: filename: ' . $rr['filename'], LOGGER_DATA); - + //logger('filename: ' . $rr['filename'], LOGGER_DEBUG); if ($rr['flags'] & ATTACH_FLAG_DIR) { // @todo can't we drop '/cloud'? it gets stripped off anyway in RedDirectory - $ret[] = new RedDirectory('/cloud' . $path . '/' . $rr['filename'], $auth); + $ret[] = new RedDAV\RedDirectory('/cloud' . $path . '/' . $rr['filename'], $auth); } else { - $ret[] = new RedFile('/cloud' . $path . '/' . $rr['filename'], $rr, $auth); + $ret[] = new RedDAV\RedFile('/cloud' . $path . '/' . $rr['filename'], $rr, $auth); } } @@ -867,12 +178,17 @@ function RedCollectionData($file, &$auth) { /** * @brief TODO What exactly is this function for? * + * @fixme function name looks like a class name, should we rename it? + * * @param string $file - * @param &$auth + * path to file or directory + * @param RedBasicAuth &$auth * @param boolean $test (optional) enable test mode + * @return RedFile|RedDirectory|boolean|null + * @throw \Sabre\DAV\Exception\Forbidden */ function RedFileData($file, &$auth, $test = false) { - logger('RedFileData:' . $file . (($test) ? ' (test mode) ' : ''), LOGGER_DEBUG); + logger($file . (($test) ? ' (test mode) ' : ''), LOGGER_DATA); $x = strpos($file, '/cloud'); if ($x === 0) { @@ -880,13 +196,13 @@ function RedFileData($file, &$auth, $test = false) { } if ((! $file) || ($file === '/')) { - return new RedDirectory('/', $auth); + return new RedDAV\RedDirectory('/', $auth); } $file = trim($file, '/'); $path_arr = explode('/', $file); - + if (! $path_arr) return null; @@ -925,7 +241,7 @@ function RedFileData($file, &$auth, $test = false) { if ($r && ( $r[0]['flags'] & ATTACH_FLAG_DIR)) { $folder = $r[0]['hash']; $path = $path . '/' . $r[0]['filename']; - } + } if (! $r) { $r = q("select id, uid, hash, filename, filetype, filesize, revision, folder, flags, created, edited from attach where folder = '%s' and filename = '%s' and uid = %d $perms group by filename limit 1", @@ -951,15 +267,15 @@ function RedFileData($file, &$auth, $test = false) { if ($test) return true; // final component was a directory. - return new RedDirectory('/cloud/' . $file, $auth); + return new RedDAV\RedDirectory('/cloud/' . $file, $auth); } if ($errors) { - logger('RedFileData: not found'); + logger('not found ' . $file); if ($test) return false; if ($permission_error) { - logger('RedFileData: permission error'); + logger('permission error ' . $file); throw new DAV\Exception\Forbidden('Permission denied.'); } return; @@ -971,212 +287,10 @@ function RedFileData($file, &$auth, $test = false) { if ($r[0]['flags'] & ATTACH_FLAG_DIR) { // @todo can't we drop '/cloud'? it gets stripped off anyway in RedDirectory - return new RedDirectory('/cloud' . $path . '/' . $r[0]['filename'], $auth); + return new RedDAV\RedDirectory('/cloud' . $path . '/' . $r[0]['filename'], $auth); } else { - return new RedFile('/cloud' . $path . '/' . $r[0]['filename'], $r[0], $auth); + return new RedDAV\RedFile('/cloud' . $path . '/' . $r[0]['filename'], $r[0], $auth); } } return false; -} - - - -/** - * @brief Authentication backend class for RedDAV. - * - * This class also contains some data which is not necessary for authentication - * like timezone settings. - * - */ -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 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 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); - } - -} // class RedBasicAuth +}
\ No newline at end of file diff --git a/include/taxonomy.php b/include/taxonomy.php index 92003328f..35605da22 100644 --- a/include/taxonomy.php +++ b/include/taxonomy.php @@ -154,7 +154,7 @@ function tagadelic($uid, $count = 0, $authors = '', $flags = 0, $restrict = 0, $ } function tags_sort($a,$b) { - if($a[0] == $b[0]) + if(strtolower($a[0]) == strtolower($b[0])) return 0; return((strtolower($a[0]) < strtolower($b[0])) ? -1 : 1); } diff --git a/include/text.php b/include/text.php index 0e38de2d2..fca23ca22 100644 --- a/include/text.php +++ b/include/text.php @@ -619,8 +619,8 @@ function get_tags($s) { } if(substr($mtch,-1,1) === '.') $mtch = substr($mtch,0,-1); - // ignore strictly numeric tags like #1 - if((strpos($mtch,'#') === 0) && ( ctype_digit(substr($mtch,1)) || substr($mtch,1,1) === '^')) + // ignore strictly numeric tags like #1 or #^ bookmarks or ## double hash + if((strpos($mtch,'#') === 0) && ( ctype_digit(substr($mtch,1)) || substr($mtch,1,1) === '^') || substr($mtch,1,1) === '#') continue; // try not to catch url fragments if(strpos($s,$mtch) && preg_match('/[a-zA-z0-9\/]/',substr($s,strpos($s,$mtch)-1,1))) diff --git a/include/widgets.php b/include/widgets.php index 8905df59a..2e406aa77 100644 --- a/include/widgets.php +++ b/include/widgets.php @@ -430,6 +430,8 @@ function widget_settings_menu($arr) { // Retrieve the 'self' address book entry for use in the auto-permissions link + $role = get_pconfig(local_user(),'system','permissions_role'); + $abk = q("select abook_id from abook where abook_channel = %d and ( abook_flags & %d ) limit 1", intval(local_user()), intval(ABOOK_FLAG_SELF) @@ -487,14 +489,15 @@ function widget_settings_menu($arr) { 'selected' => '' ), - array( + ); + + if($role === false || $role === 'custom') { + $tabs[] = array( 'label' => t('Automatic Permissions (Advanced)'), 'url' => $a->get_baseurl(true) . '/connedit/' . $abook_self_id, 'selected' => '' - ), - - - ); + ); + } if(feature_enabled(local_user(),'premium_channel')) { $tabs[] = array( diff --git a/include/zot.php b/include/zot.php index fda2a2bea..24ace9cbb 100644 --- a/include/zot.php +++ b/include/zot.php @@ -2,6 +2,7 @@ require_once('include/crypto.php'); require_once('include/items.php'); +require_once('include/hubloc.php'); /** * Red implementation of zot protocol. @@ -103,7 +104,7 @@ function zot_get_hublocs($hash) { * @returns string json encoded zot packet */ -function zot_build_packet($channel,$type = 'notify',$recipients = null, $remote_key = null, $secret = null) { +function zot_build_packet($channel,$type = 'notify',$recipients = null, $remote_key = null, $secret = null, $extra = null) { $data = array( 'type' => $type, @@ -128,6 +129,12 @@ function zot_build_packet($channel,$type = 'notify',$recipients = null, $remote_ $data['secret_sig'] = base64url_encode(rsa_sign($secret,$channel['channel_prvkey'])); } + if($extra) { + foreach($extra as $k => $v) + $data[$k] = $v; + } + + logger('zot_build_packet: ' . print_r($data,true), LOGGER_DATA); // Hush-hush ultra top-secret mode @@ -1074,6 +1081,7 @@ function zot_import($arr, $sender_url) { continue; } + $message_request = ((array_key_exists('message_id',$i['notify'])) ? true : false); $i['notify']['sender']['hash'] = make_xchan_hash($i['notify']['sender']['guid'],$i['notify']['sender']['guid_sig']); $deliveries = null; @@ -1175,7 +1183,7 @@ function zot_import($arr, $sender_url) { logger('Activity recipients: ' . print_r($deliveries,true), LOGGER_DATA); $relay = ((array_key_exists('flags',$i['message']) && in_array('relay',$i['message']['flags'])) ? true : false); - $result = process_delivery($i['notify']['sender'],$arr,$deliveries,$relay); + $result = process_delivery($i['notify']['sender'],$arr,$deliveries,$relay,false,$message_request); } elseif($i['message']['type'] === 'mail') { @@ -1372,7 +1380,7 @@ function allowed_public_recips($msg) { } -function process_delivery($sender,$arr,$deliveries,$relay,$public = false) { +function process_delivery($sender,$arr,$deliveries,$relay,$public = false,$request = false) { $result = array(); @@ -1435,6 +1443,16 @@ function process_delivery($sender,$arr,$deliveries,$relay,$public = false) { ); if(! $r) { $result[] = array($d['hash'],'comment parent not found',$channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>',$arr['mid']); + + // We don't seem to have a copy of this conversation or at least the parent - so request a copy of the entire conversation to date. + // Don't do this if it's a relay post as we're the ones who are supposed to have the copy and we don't want the request to loop. + // Also don't do this if this comment came from a conversation request packet. It's possible that comments are allowed but posting + // isn't and that could cause a conversation fetch loop. We can detect these packets since they are delivered via a 'notify' packet type + // that has a message_id element in the initial zot packet (just like the corresponding 'request' packet type which makes the request). + + if((! $relay) && (! $request)) + proc_run('php', 'include/notifier.php', 'request', $channel['channel_id'], $sender['hash'], $arr['parent_mid']); + continue; } if($relay) { @@ -1475,11 +1493,13 @@ function process_delivery($sender,$arr,$deliveries,$relay,$public = false) { continue; } - $r = q("select id, edited from item where mid = '%s' and uid = %d limit 1", + $r = q("select id, edited, item_flags, mid, parent_mid from item where mid = '%s' and uid = %d limit 1", dbesc($arr['mid']), intval($channel['channel_id']) ); if($r) { + // We already have this post. + // Maybe it has been edited? $item_id = $r[0]['id']; if($arr['edited'] > $r[0]['edited']) { $arr['id'] = $r[0]['id']; @@ -1491,6 +1511,10 @@ function process_delivery($sender,$arr,$deliveries,$relay,$public = false) { } else { $result[] = array($d['hash'],'update ignored',$channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>',$arr['mid']); + // We need this line to ensure wall-to-wall comments are relayed (by falling through to the relay bit), + // and at the same time not relay any other relayable posts more than once, because to do so is very wasteful. + if(! ($r[0]['item_flags'] & ITEM_ORIGIN)) + continue; } } else { @@ -1714,9 +1738,14 @@ function process_location_delivery($sender,$arr,$deliveries) { ); if($r) $sender['key'] = $r[0]['xchan_pubkey']; - - $x = sync_locations($sender,$arr,true); - logger('process_location_delivery: results: ' . print_r($x,true), LOGGER_DATA); + if(array_key_exists('locations',$arr) && $arr['locations']) { + $x = sync_locations($sender,$arr,true); + logger('process_location_delivery: results: ' . print_r($x,true), LOGGER_DEBUG); + if($x['changed']) { + $guid = random_string() . '@' . get_app()->get_hostname(); + update_modtime($sender['hash'],$sender['guid'],$arr['locations'][0]['address'],UPDATE_FLAGS_UPDATED); + } + } } @@ -1740,6 +1769,11 @@ function sync_locations($sender,$arr,$absolute = false) { } } + // Ensure that they have one primary hub + + if(! $has_primary) + $arr['locations'][0]['primary'] = true; + foreach($arr['locations'] as $location) { if(! rsa_verify($location['url'],base64url_decode($location['url_sig']),$sender['key'])) { logger('sync_locations: Unable to verify site signature for ' . $location['url']); @@ -1747,10 +1781,6 @@ function sync_locations($sender,$arr,$absolute = false) { continue; } - // Ensure that they have one primary hub - - if(! $has_primary) - $location['primary'] = true; for($x = 0; $x < count($xisting); $x ++) { if(($xisting[$x]['hubloc_url'] === $location['url']) @@ -1824,22 +1854,35 @@ function sync_locations($sender,$arr,$absolute = false) { q("delete from hubloc where hubloc_id = %d limit 1", intval($r[$h]['hubloc_id']) ); + $what .= 'duplicate_hubloc_removed '; + $changed = true; } } if((($r[0]['hubloc_flags'] & HUBLOC_FLAGS_PRIMARY) && (! $location['primary'])) || ((! ($r[0]['hubloc_flags'] & HUBLOC_FLAGS_PRIMARY)) && ($location['primary']))) { - $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d), hubloc_updated = '%s' where hubloc_id = %d limit 1", + $m = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d), hubloc_updated = '%s' where hubloc_id = %d limit 1", intval(HUBLOC_FLAGS_PRIMARY), dbesc(datetime_convert()), intval($r[0]['hubloc_id']) ); + // make sure hubloc_change_primary() has current data + $r[0]['hubloc_flags'] = $r[0]['hubloc_flags'] ^ HUBLOC_FLAGS_PRIMARY; + hubloc_change_primary($r[0]); $what .= 'primary_hub '; $changed = true; } + elseif($absolute) { + // Absolute sync - make sure the current primary is correctly reflected in the xchan + $pr = hubloc_change_primary($r[0]); + if($pr) { + $what .= 'xchan_primary'; + $changed = true; + } + } if((($r[0]['hubloc_flags'] & HUBLOC_FLAGS_DELETED) && (! $location['deleted'])) || ((! ($r[0]['hubloc_flags'] & HUBLOC_FLAGS_DELETED)) && ($location['deleted']))) { - $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d), hubloc_updated = '%s' where hubloc_id = %d limit 1", + $n = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d), hubloc_updated = '%s' where hubloc_id = %d limit 1", intval(HUBLOC_FLAGS_DELETED), dbesc(datetime_convert()), intval($r[0]['hubloc_id']) @@ -1850,7 +1893,8 @@ function sync_locations($sender,$arr,$absolute = false) { continue; } - // new hub claiming to be primary. Make it so. + // Existing hubs are dealt with. Now let's process any new ones. + // New hub claiming to be primary. Make it so by removing any existing primaries. if(intval($location['primary'])) { $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d), hubloc_updated = '%s' where hubloc_hash = '%s' and (hubloc_flags & %d )", @@ -1880,6 +1924,14 @@ function sync_locations($sender,$arr,$absolute = false) { $what .= 'newhub '; $changed = true; + if($location['primary']) { + $r = q("select * from hubloc where hubloc_addr = '%s' and hubloc_sitekey = '%s' limit 1", + dbesc($location['address']), + dbesc($location['sitekey']) + ); + if($r) + hubloc_change_primary($r[0]); + } } // get rid of any hubs we have for this channel which weren't reported. @@ -1893,7 +1945,7 @@ function sync_locations($sender,$arr,$absolute = false) { dbesc(datetime_convert()), intval($x['hubloc_id']) ); - $what .= 'removed_hub'; + $what .= 'removed_hub '; $changed = true; } } @@ -2758,3 +2810,75 @@ function import_author_zot($x) { return false; } +function zot_process_message_request($data) { + $ret = array('success' => false); + +// note: disabled until the loops stop. + return $ret; + + if(! $data['message_id']) { + $ret['message'] = 'no message_id'; + logger('no message_id'); + return $ret; + } + + $sender = $data['sender']; + $sender_hash = make_xchan_hash($sender['guid'],$sender['guid_sig']); + + $arr = $data['recipients'][0]; + $recip_hash = make_xchan_hash($arr['guid'],$arr['guid_sig']); + $c = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_hash = '%s' limit 1", + dbesc($recip_hash) + ); + if(! $c) { + logger('recipient channel not found.'); + $ret['message'] .= 'recipient not found.' . EOL; + return $ret; + } + $messages = zot_feed($c[0]['channel_id'],$sender_hash,array('message_id' => $data['message_id'])); + if($messages) { + $env_recips = null; + + $r = q("select hubloc_guid, hubloc_url, hubloc_sitekey, hubloc_network, hubloc_flags, hubloc_callback, hubloc_host + from hubloc where hubloc_hash = '" . dbesc($sender_hash) . "' and not (hubloc_flags & %d) + and not (hubloc_status & %d) group by hubloc_sitekey", + intval(HUBLOC_FLAGS_DELETED), + intval(HUBLOC_OFFLINE) + ); + if(! $r) { + logger('no hubs'); + return $ret; + } + $hubs = $r; + $hublist = array(); + $keys = array(); + + $private = ((array_key_exists('flags',$messages[0]) && in_array('private',$messages[0]['flags'])) ? true : false); + if($private) + $env_recips = array('guid' => $sender['guid'],'guid_sig' => $sender['guid_sig'],'hash' => $sender_hash); + $data_packet = json_encode(array('message_list' => $messages)); + + foreach($hubs as $hub) { + $hash = random_string(); + $n = zot_build_packet($c[0],'notify',$env_recips,(($private) ? $hub['hubloc_sitekey'] : null),$hash,array('message_id' => $data['message_id'])); + q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, + outq_created, outq_updated, outq_notify, outq_msg ) + values ( '%s', %d, %d, '%s', '%s', %d, '%s', '%s', '%s', '%s' )", + dbesc($hash), + intval($c[0]['channel_account_id']), + intval($c[0]['channel_id']), + dbesc('zot'), + dbesc($hub['hubloc_callback']), + intval(1), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($n), + dbesc($data_packet) + ); + proc_run('php','include/deliver.php',$hash); + } + + } + $ret['success'] = true; + return $ret; +} |