diff options
Diffstat (limited to 'Zotlabs')
-rw-r--r-- | Zotlabs/Lib/Enotify.php | 14 | ||||
-rw-r--r-- | Zotlabs/Lib/Libzot.php | 401 | ||||
-rw-r--r-- | Zotlabs/Lib/PConfig.php | 107 | ||||
-rw-r--r-- | Zotlabs/Module/Owa.php | 10 | ||||
-rw-r--r-- | Zotlabs/Module/Photo.php | 78 | ||||
-rw-r--r-- | Zotlabs/Module/Photos.php | 12 | ||||
-rw-r--r-- | Zotlabs/Module/Search.php | 10 | ||||
-rw-r--r-- | Zotlabs/Update/_1225.php | 26 |
8 files changed, 479 insertions, 179 deletions
diff --git a/Zotlabs/Lib/Enotify.php b/Zotlabs/Lib/Enotify.php index cfb0bd344..25c96d9cc 100644 --- a/Zotlabs/Lib/Enotify.php +++ b/Zotlabs/Lib/Enotify.php @@ -825,7 +825,7 @@ class Enotify { // convert this logic into a json array just like the system notifications - return array( + $x = array( 'notify_link' => $item['llink'], 'name' => $item['author']['xchan_name'], 'url' => $item['author']['xchan_url'], @@ -835,9 +835,19 @@ class Enotify { 'b64mid' => ((in_array($item['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) ? 'b64.' . base64url_encode($item['thr_parent']) : 'b64.' . base64url_encode($item['mid'])), 'notify_id' => 'undefined', 'thread_top' => (($item['item_thread_top']) ? true : false), - 'message' => strip_tags(bbcode($itemem_text)) + 'message' => strip_tags(bbcode($itemem_text)), + // these are for the superblock addon + 'hash' => $item['author']['xchan_hash'], + 'uid' => local_channel(), + 'display' => true ); + call_hooks('enotify_format',$x); + if(! $x['display']) { + return []; + } + + return $x; } } diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php index ec9db4ce1..2c726aff4 100644 --- a/Zotlabs/Lib/Libzot.php +++ b/Zotlabs/Lib/Libzot.php @@ -7,16 +7,10 @@ namespace Zotlabs\Lib; * */ -use Zotlabs\Lib\DReport; -use Zotlabs\Lib\Enotify; -use Zotlabs\Lib\Group; -use Zotlabs\Lib\Libsync; -use Zotlabs\Lib\Libzotdir; -use Zotlabs\Lib\System; -use Zotlabs\Lib\MessageFilter; -use Zotlabs\Lib\Queue; -use Zotlabs\Lib\Zotfinger; -use Zotlabs\Web\HTTPSig; +use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Access\Permissions; +use Zotlabs\Access\PermissionLimits; +use Zotlabs\Daemon\Master; require_once('include/crypto.php'); @@ -211,9 +205,10 @@ class Libzot { if($channel) { $headers = [ - 'X-Zot-Token' => random_string(), - 'Digest' => HTTPSig::generate_digest_header($data), - 'Content-type' => 'application/x-zot+json' + 'X-Zot-Token' => random_string(), + 'Digest' => HTTPSig::generate_digest_header($data), + 'Content-type' => 'application/x-zot+json', + '(request-target)' => 'post ' . get_request_string($url) ]; $h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel),false,'sha512', @@ -378,13 +373,13 @@ class Libzot { else { // if we were just granted read stream permission and didn't have it before, try to pull in some posts if((! $old_read_stream_perm) && (intval($permissions['view_stream']))) - \Zotlabs\Daemon\Master::Summon(array('Onepoll',$r[0]['abook_id'])); + Master::Summon([ 'Onepoll', $r[0]['abook_id'] ]); } } else { - $p = \Zotlabs\Access\Permissions::connect_perms($channel['channel_id']); - $my_perms = \Zotlabs\Access\Permissions::serialise($p['perms']); + $p = Permissions::connect_perms($channel['channel_id']); + $my_perms = Permissions::serialise($p['perms']); $automatic = $p['automatic']; @@ -424,8 +419,8 @@ class Libzot { ); if($new_connection) { - if(! \Zotlabs\Access\Permissions::PermsCompare($new_perms,$previous_perms)) - \Zotlabs\Daemon\Master::Summon(array('Notifier','permissions_create',$new_connection[0]['abook_id'])); + if(! Permissions::PermsCompare($new_perms,$previous_perms)) + Master::Summon([ 'Notifier', 'permissions_create', $new_connection[0]['abook_id'] ]); Enotify::submit( [ 'type' => NOTIFY_INTRO, @@ -438,7 +433,7 @@ class Libzot { if(intval($permissions['view_stream'])) { if(intval(get_pconfig($channel['channel_id'],'perm_limits','send_stream') & PERMS_PENDING) || (! intval($new_connection[0]['abook_pending']))) - \Zotlabs\Daemon\Master::Summon(array('Onepoll',$new_connection[0]['abook_id'])); + Master::Summon([ 'Onepoll', $new_connection[0]['abook_id'] ]); } @@ -975,39 +970,45 @@ class Libzot { $x = json_decode($x,true); } - if(! $x['success']) { + if(! is_array($x)) { + btlogger('failed communication - no response'); + } - // handle remote validation issues + if($x) { + if(! $x['success']) { - $b = q("update dreport set dreport_result = '%s', dreport_time = '%s' where dreport_queue = '%s'", - dbesc(($x['message']) ? $x['message'] : 'unknown delivery error'), - dbesc(datetime_convert()), - dbesc($outq['outq_hash']) - ); - } + // handle remote validation issues + + $b = q("update dreport set dreport_result = '%s', dreport_time = '%s' where dreport_queue = '%s'", + dbesc(($x['message']) ? $x['message'] : 'unknown delivery error'), + dbesc(datetime_convert()), + dbesc($outq['outq_hash']) + ); + } - if(array_key_exists('delivery_report',$x) && is_array($x['delivery_report'])) { - foreach($x['delivery_report'] as $xx) { - if(is_array($xx) && array_key_exists('message_id',$xx) && DReport::is_storable($xx)) { - q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_name, dreport_result, dreport_time, dreport_xchan ) values ( '%s', '%s', '%s','%s','%s','%s','%s' ) ", - dbesc($xx['message_id']), - dbesc($xx['location']), - dbesc($xx['recipient']), - dbesc($xx['name']), - dbesc($xx['status']), - dbesc(datetime_convert($xx['date'])), - dbesc($xx['sender']) - ); + if(is_array($x) && array_key_exists('delivery_report',$x) && is_array($x['delivery_report'])) { + foreach($x['delivery_report'] as $xx) { + call_hooks('dreport_process',$xx); + if(is_array($xx) && array_key_exists('message_id',$xx) && DReport::is_storable($xx)) { + q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_name, dreport_result, dreport_time, dreport_xchan ) values ( '%s', '%s', '%s','%s','%s','%s','%s' ) ", + dbesc($xx['message_id']), + dbesc($xx['location']), + dbesc($xx['recipient']), + dbesc($xx['name']), + dbesc($xx['status']), + dbesc(datetime_convert($xx['date'])), + dbesc($xx['sender']) + ); + } } - } - // we have a more descriptive delivery report, so discard the per hub 'queue' report. + // we have a more descriptive delivery report, so discard the per hub 'queue' report. - q("delete from dreport where dreport_queue = '%s' ", - dbesc($outq['outq_hash']) - ); + q("delete from dreport where dreport_queue = '%s' ", + dbesc($outq['outq_hash']) + ); + } } - // update the timestamp for this site q("update site set site_dead = 0, site_update = '%s' where site_url = '%s'", @@ -1092,13 +1093,27 @@ class Libzot { return; } - $message_request = ((array_key_exists('message_id',$env)) ? true : false); - if($message_request) - logger('processing message request'); + $message_request = false; + $has_data = array_key_exists('data',$env) && $env['data']; $data = (($has_data) ? $env['data'] : false); - + + $AS = null; + + if($env['encoding'] === 'activitystreams') { + + $AS = new ActivityStreams($data); + if(! $AS->is_valid()) { + logger('Activity rejected: ' . print_r($data,true)); + return; + } + $arr = Activity::decode_note($AS); + + logger($AS->debug()); + } + + $deliveries = null; if(array_key_exists('recipients',$env) && count($env['recipients'])) { @@ -1140,7 +1155,7 @@ class Libzot { // and who are allowed to see them based on the sender's permissions // @fixme; - $deliveries = self::public_recips($env); + $deliveries = self::public_recips($env,$AS); } @@ -1157,48 +1172,42 @@ class Libzot { if(in_array($env['type'],['activity','response'])) { - if($env['encoding'] === 'zot') { - $arr = get_item_elements($data); - - $v = validate_item_elements($data,$arr); - - if(! $v['success']) { - logger('Activity rejected: ' . $v['message'] . ' ' . print_r($data,true)); - return; - } - } - elseif($env['encoding'] === 'activitystreams') { + $arr = Activity::decode_note($AS); - $AS = new \Zotlabs\Lib\ActivityStreams($data); - if(! $AS->is_valid()) { - logger('Activity rejected: ' . print_r($data,true)); - return; - } - $arr = \Zotlabs\Lib\Activity::decode_note($AS); + //logger($AS->debug()); - logger($AS->debug()); + $r = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($AS->actor['id']) + ); - $r = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", - dbesc($AS->actor['id']) - ); + if($r) { + $arr['author_xchan'] = $r[0]['hubloc_hash']; + } - if($r) { - $arr['author_xchan'] = $r[0]['hubloc_hash']; - } - // @fixme (in individual delivery, change owner if needed) + + $s = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($env['sender']) + ); + + // in individual delivery, change owner if needed + if($s) { + $arr['owner_xchan'] = $s[0]['hubloc_hash']; + } + else { $arr['owner_xchan'] = $env['sender']; - if($private) { - $arr['item_private'] = true; - } - // @fixme - spoofable - if($AS->data['hubloc']) { - $arr['item_verified'] = true; - } - if($AS->data['signed_data']) { - IConfig::Set($arr,'activitystreams','signed_data',$AS->data['signed_data'],false); - } + } + if($private) { + $arr['item_private'] = true; + } + // @fixme - spoofable + if($AS->data['hubloc']) { + $arr['item_verified'] = true; } + if($AS->data['signed_data']) { + IConfig::Set($arr,'activitystreams','signed_data',$AS->data['signed_data'],false); + } + logger('Activity received: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); logger('Activity recipients: ' . print_r($deliveries,true), LOGGER_DATA, LOG_DEBUG); @@ -1225,15 +1234,31 @@ class Libzot { } - static function is_top_level($env) { + static function is_top_level($env,$act) { if($env['encoding'] === 'zot' && array_key_exists('flags',$env) && in_array('thread_parent', $env['flags'])) { return true; } - if($env['encoding'] === 'activitystreams') { - if(array_key_exists('inReplyTo',$env['data']) && $env['data']['inReplyTo']) { + if($act) { + if(in_array($act->type, ['Like','Dislike'])) { return false; } - return true; + $x = self::find_parent($env,$act); + if($x === $act->id || $x === $act->obj['id']) { + return true; + } + } + return false; + } + + + static function find_parent($env,$act) { + if($act) { + if(in_array($act->type, ['Like','Dislike'])) { + return $act->obj['id']; + } + if($act->parent_id) { + return $act->parent_id; + } } return false; } @@ -1255,7 +1280,7 @@ class Libzot { * @return NULL|array */ - static function public_recips($msg) { + static function public_recips($msg, $act) { require_once('include/channel.php'); @@ -1269,7 +1294,7 @@ class Libzot { $perm = 'send_stream'; - if(self::is_top_level($msg)) { + if(self::is_top_level($msg,$act)) { $check_mentions = true; } } @@ -1301,9 +1326,9 @@ class Libzot { if($check_mentions) { // It's a top level post. Look at the tags. See if any of them are mentions and are on this hub. - if(array_path_exists('data/object/tag',$msg)) { - if(is_array($msg['data']['object']['tag']) && $msg['data']['object']['tag']) { - foreach($msg['data']['object']['tag'] as $tag) { + if($act && $act->obj) { + if(is_array($act->obj['tag']) && $act->obj['tag']) { + foreach($act->obj['tag'] as $tag) { if($tag['type'] === 'Mention' && (strpos($tag['href'],z_root()) !== false)) { $address = basename($tag['href']); if($address) { @@ -1325,9 +1350,12 @@ class Libzot { // everybody that stored a copy of the parent. This way we know we're covered. We'll check the // comment permissions when we deliver them. - if(array_path_exists('data/inReplyTo',$msg)) { - $z = q("select owner_xchan as hash from item where parent_mid = '%s' ", - dbesc($msg['data']['inReplyTo']) + $thread_parent = self::find_parent($msg,$act); + + if($thread_parent) { + $z = q("select channel_hash as hash from channel left join item on channel.channel_id = item.uid where ( item.thr_parent = '%s' OR item.parent_mid = '%s' ) ", + dbesc($thread_parent), + dbesc($thread_parent) ); if($z) { foreach($z as $zv) { @@ -1341,7 +1369,7 @@ class Libzot { // It's a bit of work since it's a multi-dimensional array if($r) { - $r = array_unique($r); + $r = array_values(array_unique($r)); } logger('public_recips: ' . print_r($r,true), LOGGER_DATA, LOG_DEBUG); @@ -1378,7 +1406,7 @@ class Libzot { $local_public = $public; - $DR = new \Zotlabs\Lib\DReport(z_root(),$sender,$d,$arr['mid']); + $DR = new DReport(z_root(),$sender,$d,$arr['mid']); $channel = channelx_by_hash($d); @@ -1413,7 +1441,7 @@ class Libzot { $local_public = true; $r = q("select xchan_selfcensored from xchan where xchan_hash = '%s' limit 1", - dbesc($sender['hash']) + dbesc($sender) ); // don't import sys channel posts from selfcensored authors if($r && (intval($r[0]['xchan_selfcensored']))) { @@ -1441,11 +1469,30 @@ class Libzot { $arr['item_wall'] = 0; } - if((! perm_is_allowed($channel['channel_id'],$sender,$perm)) && (! $tag_delivery) && (! $local_public)) { - logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}"); - $DR->update('permission denied'); - $result[] = $DR->get(); - continue; + $friendofriend = false; + + if ((! $tag_delivery) && (! $local_public)) { + $allowed = (perm_is_allowed($channel['channel_id'],$sender,$perm)); + if((! $allowed) && $perm === 'post_comments') { + $parent = q("select * from item where mid = '%s' and uid = %d limit 1", + dbesc($arr['parent_mid']), + intval($channel['channel_id']) + ); + if ($parent) { + $allowed = can_comment_on_post($d,$parent[0]); + } + } + if($request) { + $allowed = true; + $friendofriend = true; + } + + if (! $allowed) { + logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}"); + $DR->update('permission denied'); + $result[] = $DR->get(); + continue; + } } if($arr['mid'] != $arr['parent_mid']) { @@ -1456,7 +1503,7 @@ class Libzot { // As a side effect we will also do a preliminary check that we have the top-level-post, otherwise // processing it is pointless. - $r = q("select route, id from item where mid = '%s' and uid = %d limit 1", + $r = q("select route, id, owner_xchan, item_private from item where mid = '%s' and uid = %d limit 1", dbesc($arr['parent_mid']), intval($channel['channel_id']) ); @@ -1480,15 +1527,21 @@ class Libzot { if((! $relay) && (! $request) && (! $local_public) && perm_is_allowed($channel['channel_id'],$sender,'send_stream')) { - \Zotlabs\Daemon\Master::Summon(array('Notifier', 'request', $channel['channel_id'], $sender, $arr['parent_mid'])); + self::fetch_conversation($channel,$arr['parent_mid']); } continue; } - if($relay) { + + if($relay || $friendofriend || (intval($r[0]['item_private']) === 0 && intval($arr['item_private']) === 0)) { // reset the route in case it travelled a great distance upstream // use our parent's route so when we go back downstream we'll match // with whatever route our parent has. + // Also friend-of-friend conversations may have been imported without a route, + // but we are now getting comments via listener delivery + // and if there is no privacy on this or the parent, we don't care about the route, + // so just set the owner and route accordingly. $arr['route'] = $r[0]['route']; + $arr['owner_xchan'] = $r[0]['owner_xchan']; } else { @@ -1546,13 +1599,13 @@ class Libzot { $arr['aid'] = $channel['channel_account_id']; $arr['uid'] = $channel['channel_id']; - $item_id = delete_imported_item($sender,$arr,$channel['channel_id'],$relay); + $item_id = self::delete_imported_item($sender,$arr,$channel['channel_id'],$relay); $DR->update(($item_id) ? 'deleted' : 'delete_failed'); $result[] = $DR->get(); if($relay && $item_id) { logger('process_delivery: invoking relay'); - \Zotlabs\Daemon\Master::Summon(array('Notifier','relay',intval($item_id))); + Master::Summon([ 'Notifier', 'relay', intval($item_id) ]); $DR->update('relayed'); $result[] = $DR->get(); } @@ -1662,7 +1715,7 @@ class Libzot { if($relay && $item_id) { logger('Invoking relay'); - \Zotlabs\Daemon\Master::Summon(array('Notifier','relay',intval($item_id))); + Master::Summon([ 'Notifier', 'relay', intval($item_id) ]); $DR->addto_update('relayed'); $result[] = $DR->get(); } @@ -1676,6 +1729,98 @@ class Libzot { return $result; } + static public function fetch_conversation($channel,$mid) { + + // Use Zotfinger to create a signed request + + $a = Zotfinger::exec($mid,$channel); + + logger('received conversation: ' . print_r($a,true), LOGGER_DATA); + + if($a['data']['type'] !== 'OrderedCollection') { + return; + } + + if(! intval($a['data']['totalItems'])) { + return; + } + + $ret = []; + + foreach($a['data']['orderedItems'] as $activity) { + + $AS = new ActivityStreams($activity); + if(! $AS->is_valid()) { + logger('FOF Activity rejected: ' . print_r($activity,true)); + continue; + } + $arr = Activity::decode_note($AS); + + logger($AS->debug()); + + + $r = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($AS->actor['id']) + ); + + if(! $r) { + $y = import_author_xchan([ 'url' => $AS->actor['id'] ]); + if($y) { + $r = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($AS->actor['id']) + ); + } + if(! $r) { + logger('FOF Activity: no actor'); + continue; + } + } + + if($AS->obj['actor'] && $AS->obj['actor']['id'] && $AS->obj['actor']['id'] !== $AS->actor['id']) { + $y = import_author_xchan([ 'url' => $AS->obj['actor']['id'] ]); + if(! $y) { + logger('FOF Activity: no object actor'); + continue; + } + } + + + if($r) { + $arr['author_xchan'] = $r[0]['hubloc_hash']; + } + + $s = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($a['signature']['signer']) + ); + + if($s) { + $arr['owner_xchan'] = $s[0]['hubloc_hash']; + } + else { + $arr['owner_xchan'] = $a['signature']['signer']; + } + + // @fixme - spoofable + if($AS->data['hubloc']) { + $arr['item_verified'] = true; + } + if($AS->data['signed_data']) { + IConfig::Set($arr,'activitystreams','signed_data',$AS->data['signed_data'],false); + } + + logger('FOF Activity received: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); + logger('FOF Activity recipient: ' . $channel['channel_hash'], LOGGER_DATA, LOG_DEBUG); + + $result = self::process_delivery($arr['owner_xchan'],$arr, [ $channel['channel_hash'] ],false,false,true); + if ($result) { + $ret = array_merge($ret, $result); + } + } + + return $ret; + } + + /** * @brief Remove community tag. * @@ -1900,7 +2045,7 @@ class Libzot { foreach($deliveries as $d) { - $DR = new \Zotlabs\Lib\DReport(z_root(),$sender,$d,$arr['mid']); + $DR = new DReport(z_root(),$sender,$d,$arr['mid']); $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($d['hash']) @@ -2112,7 +2257,8 @@ class Libzot { if(intval($channel['channel_removed']) && $hub['hubloc_url'] === z_root()) $hub['hubloc_deleted'] = 1; - $ret[] = [ + + $z = [ 'host' => $hub['hubloc_host'], 'address' => $hub['hubloc_addr'], 'id_url' => $hub['hubloc_id_url'], @@ -2120,10 +2266,25 @@ class Libzot { 'url' => $hub['hubloc_url'], 'url_sig' => $hub['hubloc_url_sig'], 'site_id' => $hub['hubloc_site_id'], - 'callback' => $hub['hubloc_callback'], + 'callback' => $hub['hubloc_url'] . '/zot', 'sitekey' => $hub['hubloc_sitekey'], 'deleted' => (intval($hub['hubloc_deleted']) ? true : false) ]; + + // version compatibility tweaks + + if(! strpos($z['url_sig'],'.')) { + $z['url_sig'] = 'sha256.' . $z['url_sig']; + } + + if(! $z['id_url']) { + $z['id_url'] = $z['url'] . '/channel/' . substr($z['address'],0,strpos($z['address'],'@')); + } + if(! $z['site_id']) { + $z['site_id'] = Libzot::make_xchan_hash($z['url'],$z['sitekey']); + } + + $ret[] = $z; } } @@ -2331,6 +2492,10 @@ class Libzot { // we may only end up with one; which results in posts with no author name or photo and are a bit // of a hassle to repair. If either or both are missing, do a full discovery probe. + if(! array_key_exists('id',$x)) { + return import_author_activitypub($x); + } + $hash = self::make_xchan_hash($x['id'],$x['key']); $desturl = $x['url']; @@ -2502,7 +2667,7 @@ class Libzot { } else { // check if it has characteristics of a public forum based on custom permissions. - $m = \Zotlabs\Access\Permissions::FilledAutoperms($e['channel_id']); + $m = Permissions::FilledAutoperms($e['channel_id']); if($m) { foreach($m as $k => $v) { if($k == 'tag_deliver' && intval($v) == 1) @@ -2584,13 +2749,13 @@ class Libzot { ]; $ret['channel_role'] = get_pconfig($e['channel_id'],'system','permissions_role','custom'); - + $ret['protocols'] = [ 'zot6' ]; $ret['searchable'] = $searchable; $ret['adult_content'] = $adult_channel; $ret['public_forum'] = $public_forum; - $ret['comments'] = map_scope(\Zotlabs\Access\PermissionLimits::Get($e['channel_id'],'post_comments')); - $ret['mail'] = map_scope(\Zotlabs\Access\PermissionLimits::Get($e['channel_id'],'post_mail')); + $ret['comments'] = map_scope(PermissionLimits::Get($e['channel_id'],'post_comments')); + $ret['mail'] = map_scope(PermissionLimits::Get($e['channel_id'],'post_mail')); if($deleted) $ret['deleted'] = $deleted; diff --git a/Zotlabs/Lib/PConfig.php b/Zotlabs/Lib/PConfig.php index ec0792ce1..b9384cf6b 100644 --- a/Zotlabs/Lib/PConfig.php +++ b/Zotlabs/Lib/PConfig.php @@ -57,6 +57,7 @@ class PConfig { \App::$config[$uid][$c]['config_loaded'] = true; } \App::$config[$uid][$c][$k] = $rr['v']; + \App::$config[$uid][$c]['pcfgud:'.$k] = $rr['updated']; } } } @@ -113,7 +114,7 @@ class PConfig { * The value to store * @return mixed Stored $value or false */ - static public function Set($uid, $family, $key, $value) { + static public function Set($uid, $family, $key, $value, $updated=NULL) { // this catches subtle errors where this function has been called // with local_channel() when not logged in (which returns false) @@ -130,29 +131,74 @@ class PConfig { $dbvalue = ((is_array($value)) ? serialize($value) : $value); $dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue); + if (! $updated) { + $updated = datetime_convert(); + } + + $hash = hash('sha256',$family.':'.$key); + + if (self::Get($uid, 'hz_delpconfig', $hash) !== false) { + if (Get($uid, 'hz_delpconfig', $hash) > $updated) { + logger('Refusing to update pconfig with outdated info (Item deleted more recently).', LOGGER_NORMAL, LOG_ERR); + return self::Get($uid,$family,$key); + } else { + self::Delete($uid,'hz_delpconfig',$hash); + } + } + if(self::Get($uid, $family, $key) === false) { if(! array_key_exists($uid, \App::$config)) \App::$config[$uid] = array(); if(! array_key_exists($family, \App::$config[$uid])) \App::$config[$uid][$family] = array(); - $ret = q("INSERT INTO pconfig ( uid, cat, k, v ) VALUES ( %d, '%s', '%s', '%s' ) ", + + $ret = q("INSERT INTO pconfig ( uid, cat, k, v, updated ) VALUES ( %d, '%s', '%s', '%s', '%s' ) ", intval($uid), dbesc($family), dbesc($key), - dbesc($dbvalue) + dbesc($dbvalue), + dbesc($updated) ); + + // There is a possible race condition if another process happens + // to insert something after this thread has Loaded and now. We should + // at least make a note of it if it happens. + + if (!$ret) { + logger("Error: Insert to pconfig failed.",LOGGER_NORMAL, LOG_ERR); + } + + \App::$config[$uid][$family]['pcfgud:'.$key] = $updated; + } else { + $new = (\App::$config[$uid][$family]['pcfgud:'.$key] < $updated); - $ret = q("UPDATE pconfig SET v = '%s' WHERE uid = %d and cat = '%s' AND k = '%s'", - dbesc($dbvalue), - intval($uid), - dbesc($family), - dbesc($key) - ); + if ($new) { + + // @NOTE There is still a possible race condition under limited circumstances + // where a value will be updated by another thread with more current data than + // we have. At this point there is no easy way to test for it, so we update + // and hope for the best. + + $ret = q("UPDATE pconfig SET v = '%s', updated = '%s' WHERE uid = %d and cat = '%s' AND k = '%s' ", + dbesc($dbvalue), + dbesc($updated), + intval($uid), + dbesc($family), + dbesc($key) + ); + + \App::$config[$uid][$family]['pcfgud:'.$key] = $updated; + + } else { + logger('Refusing to update pconfig with outdated info.', LOGGER_NORMAL, LOG_ERR); + return self::Get($uid, $family, $key); + } } + // keep a separate copy for all variables which were // set in the life of this page. We need this to // synchronise channel clones. @@ -163,7 +209,11 @@ class PConfig { \App::$config[$uid]['transient'][$family] = array(); \App::$config[$uid][$family][$key] = $value; - \App::$config[$uid]['transient'][$family][$key] = $value; + + if ($new) { + \App::$config[$uid]['transient'][$family][$key] = $value; + \App::$config[$uid]['transient'][$family]['pcfgud:'.$key] = $updated; + } if($ret) return $value; @@ -186,18 +236,29 @@ class PConfig { * The configuration key to delete * @return mixed */ - static public function Delete($uid, $family, $key) { + static public function Delete($uid, $family, $key, $updated = NULL) { if(is_null($uid) || $uid === false) return false; + $updated = ($updated) ? $updated : datetime_convert(); + + $newer = (\App::$config[$uid][$family]['pcfgud:'.$key] < $updated); + + if (! $newer) { + logger('Refusing to delete pconfig with outdated delete request.', LOGGER_NORMAL, LOG_ERR); + return false; + } + $ret = false; - if(array_key_exists($uid,\App::$config) - && is_array(\App::$config['uid']) - && array_key_exists($family,\App::$config['uid']) - && array_key_exists($key, \App::$config[$uid][$family])) + if (isset(\App::$config[$uid][$family][$key])) { unset(\App::$config[$uid][$family][$key]); + } + + if (isset(\App::$config[$uid][$family]['pcfgud:'.$key])) { + unset(\App::$config[$uid][$family]['pcfgud:'.$key]); + } $ret = q("DELETE FROM pconfig WHERE uid = %d AND cat = '%s' AND k = '%s'", intval($uid), @@ -205,6 +266,22 @@ class PConfig { dbesc($key) ); + if ($family != 'hz_delpconfig') { + $hash = hash('sha256',$family.':'.$key); + set_pconfig($uid,'hz_delpconfig',$hash,$updated); + } + + // Synchronize delete with clones. + + if(! array_key_exists('transient', \App::$config[$uid])) + \App::$config[$uid]['transient'] = array(); + if(! array_key_exists($family, \App::$config[$uid]['transient'])) + \App::$config[$uid]['transient'][$family] = array(); + + if ($new) { + \App::$config[$uid]['transient'][$family]['pcfgdel:'.$key] = $updated; + } + return $ret; } diff --git a/Zotlabs/Module/Owa.php b/Zotlabs/Module/Owa.php index 4a488086f..ad57f883c 100644 --- a/Zotlabs/Module/Owa.php +++ b/Zotlabs/Module/Owa.php @@ -31,15 +31,17 @@ class Owa extends \Zotlabs\Web\Controller { if($keyId) { $r = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash - where hubloc_addr = '%s' ", - dbesc(str_replace('acct:','',$keyId)) + where ( hubloc_addr = '%s' or hubloc_id_url = '%s' ) ", + dbesc(str_replace('acct:','',$keyId)), + dbesc($keyId) ); if(! $r) { $found = discover_by_webbie(str_replace('acct:','',$keyId)); if($found) { $r = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash - where hubloc_addr = '%s' ", - dbesc(str_replace('acct:','',$keyId)) + where ( hubloc_addr = '%s' or hubloc_id_url = '%s' ) ", + dbesc(str_replace('acct:','',$keyId)), + dbesc($keyId) ); } } diff --git a/Zotlabs/Module/Photo.php b/Zotlabs/Module/Photo.php index 8efc00707..30e8340e2 100644 --- a/Zotlabs/Module/Photo.php +++ b/Zotlabs/Module/Photo.php @@ -4,13 +4,12 @@ namespace Zotlabs\Module; require_once('include/security.php'); require_once('include/attach.php'); require_once('include/photo/photo_driver.php'); -require_once('include/photos.php'); class Photo extends \Zotlabs\Web\Controller { function init() { - + $prvcachecontrol = false; $streaming = null; $channel = null; @@ -32,26 +31,26 @@ class Photo extends \Zotlabs\Web\Controller { } $observer_xchan = get_observer_hash(); + $ismodified = $_SERVER['HTTP_IF_MODIFIED_SINCE']; - $default = z_root() . '/' . get_default_profile_photo(); - if(isset($type)) { /** * Profile photos - Access controls on default profile photos are not honoured since they need to be exchanged with remote sites. * */ - + + $default = get_default_profile_photo(); + if($type === 'profile') { switch($res) { - case 'm': $resolution = 5; - $default = z_root() . '/' . get_default_profile_photo(80); + $default = get_default_profile_photo(80); break; case 's': $resolution = 6; - $default = z_root() . '/' . get_default_profile_photo(48); + $default = get_default_profile_photo(48); break; case 'l': default: @@ -60,6 +59,8 @@ class Photo extends \Zotlabs\Web\Controller { } } + $modified = filemtime($default); + $default = z_root() . '/' . $default; $uid = $person; $d = [ 'imgscale' => $resolution, 'channel_id' => $uid, 'default' => $default, 'data' => '', 'mimetype' => '' ]; @@ -78,17 +79,18 @@ class Photo extends \Zotlabs\Web\Controller { intval(PHOTO_PROFILE) ); if($r) { + $modified = strtotime($r[0]['edited'] . "Z"); $data = dbunescbin($r[0]['content']); $mimetype = $r[0]['mimetype']; } if(intval($r[0]['os_storage'])) $data = file_get_contents($data); } + if(! $data) { - $data = fetch_image_from_url($default,$mimetype); - } - if(! $mimetype) { - $mimetype = 'image/png'; + $x = z_fetch_url($default,true,0,[ 'novalidate' => true ]); + $data = ($x['success'] ? $x['body'] : EMPTY_STR); + $mimetype = 'image/png'; } } else { @@ -124,9 +126,7 @@ class Photo extends \Zotlabs\Web\Controller { $photo = substr($photo,0,-2); // If viewing on a high-res screen, attempt to serve a higher resolution image: if ($resolution == 2 && ($cookie_value > 1)) - { $resolution = 1; - } } $r = q("SELECT uid, photo_usage FROM photo WHERE resource_id = '%s' AND imgscale = %d LIMIT 1", @@ -163,10 +163,13 @@ class Photo extends \Zotlabs\Web\Controller { if($exists && $allowed) { $data = dbunescbin($e[0]['content']); + $filesize = $e[0]['filesize']; $mimetype = $e[0]['mimetype']; - if(intval($e[0]['os_storage'])) { + $modified = strtotime($e[0]['edited'] . 'Z'); + if(intval($e[0]['os_storage'])) $streaming = $data; - } + if($e[0]['allow_cid'] != '' || $e[0]['allow_gid'] != '' || $e[0]['deny_gid'] != '' || $e[0]['deny_gid'] != '') + $prvcachecontrol = true; } else { if(! $allowed) { @@ -177,27 +180,40 @@ class Photo extends \Zotlabs\Web\Controller { } } + } else { + http_status_exit(404,'not found'); } } - + + header_remove('Pragma'); + + if($ismodified === gmdate("D, d M Y H:i:s", $modified) . " GMT") { + header_remove('Expires'); + header_remove('Cache-Control'); + header_remove('Set-Cookie'); + http_status_exit(304,'not modified'); + } + if(! isset($data)) { if(isset($resolution)) { switch($resolution) { - case 4: - $data = fetch_image_from_url(z_root() . '/' . get_default_profile_photo(),$mimetype); + $default = get_default_profile_photo(); break; case 5: - $data = fetch_image_from_url(z_root() . '/' . get_default_profile_photo(80),$mimetype); + $default = get_default_profile_photo(80); break; case 6: - $data = fetch_image_from_url(z_root() . '/' . get_default_profile_photo(48),$mimetype); + $default = get_default_profile_photo(48); break; default: killme(); // NOTREACHED break; } + $x = z_fetch_url(z_root() . '/' . $default,true,0,[ 'novalidate' => true ]); + $data = ($x['success'] ? $x['body'] : EMPTY_STR); + $mimetype = 'image/png'; } } @@ -210,15 +226,14 @@ class Photo extends \Zotlabs\Web\Controller { } } + // @FIXME Seems never invoked // Writing in cachefile - if (isset($cachefile) && $cachefile != '') + if (isset($cachefile) && $cachefile != '') { file_put_contents($cachefile, $data); - - if(function_exists('header_remove')) { - header_remove('Pragma'); - header_remove('pragma'); + $modified = filemtime($cachefile); } - + + header("Content-type: " . $mimetype); if($prvcachecontrol) { @@ -240,15 +255,16 @@ class Photo extends \Zotlabs\Web\Controller { // This has performance considerations but we highly recommend you // leave it alone. - $cache = get_config('system','photo_cache_time'); - if(! $cache) - $cache = (3600 * 24); // 1 day - + $cache = get_config('system','photo_cache_time', 86400); // 1 day by default + header("Expires: " . gmdate("D, d M Y H:i:s", time() + $cache) . " GMT"); header("Cache-Control: max-age=" . $cache); } + header("Last-Modified: " . gmdate("D, d M Y H:i:s", $modified) . " GMT"); + header("Content-Length: " . (isset($filesize) ? $filesize : strlen($data))); + // If it's a file resource, stream it. if($streaming && $channel) { diff --git a/Zotlabs/Module/Photos.php b/Zotlabs/Module/Photos.php index 78bfb1f09..03fd8a53d 100644 --- a/Zotlabs/Module/Photos.php +++ b/Zotlabs/Module/Photos.php @@ -263,7 +263,8 @@ class Photos extends \Zotlabs\Web\Controller { $fsize = strlen($data); } - $x = q("update photo set content = '%s', filesize = %d, height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 0", + $x = q("update photo set edited = '%s', content = '%s', filesize = %d, height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 0", + dbesc(datetime_convert()), dbescbin($data), intval($fsize), intval($height), @@ -278,7 +279,8 @@ class Photos extends \Zotlabs\Web\Controller { $width = $ph->getWidth(); $height = $ph->getHeight(); - $x = q("update photo set content = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 1", + $x = q("update photo set edited = '%s', content = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 1", + dbesc(datetime_convert()), dbescbin($ph->imageString()), intval($height), intval($width), @@ -293,7 +295,8 @@ class Photos extends \Zotlabs\Web\Controller { $width = $ph->getWidth(); $height = $ph->getHeight(); - $x = q("update photo set content = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 2", + $x = q("update photo set edited = '%s', content = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 2", + dbesc(datetime_convert()), dbescbin($ph->imageString()), intval($height), intval($width), @@ -308,7 +311,8 @@ class Photos extends \Zotlabs\Web\Controller { $width = $ph->getWidth(); $height = $ph->getHeight(); - $x = q("update photo set content = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 3", + $x = q("update photo set edited = '%s', content = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 3", + dbesc(datetime_convert()), dbescbin($ph->imageString()), intval($height), intval($width), diff --git a/Zotlabs/Module/Search.php b/Zotlabs/Module/Search.php index e520c671d..838f9d6b9 100644 --- a/Zotlabs/Module/Search.php +++ b/Zotlabs/Module/Search.php @@ -6,7 +6,7 @@ class Search extends \Zotlabs\Web\Controller { function init() { if(x($_REQUEST,'search')) - \App::$data['search'] = $_REQUEST['search']; + \App::$data['search'] = escape_tags($_REQUEST['search']); } @@ -46,12 +46,12 @@ class Search extends \Zotlabs\Web\Controller { if(x(\App::$data,'search')) $search = trim(\App::$data['search']); else - $search = ((x($_GET,'search')) ? trim(rawurldecode($_GET['search'])) : ''); + $search = ((x($_GET,'search')) ? trim(escape_tags(rawurldecode($_GET['search']))) : ''); $tag = false; if(x($_GET,'tag')) { $tag = true; - $search = ((x($_GET,'tag')) ? trim(rawurldecode($_GET['tag'])) : ''); + $search = ((x($_GET,'tag')) ? trim(escape_tags(rawurldecode($_GET['tag']))) : ''); } $static = ((array_key_exists('static',$_REQUEST)) ? intval($_REQUEST['static']) : 0); @@ -227,9 +227,9 @@ class Search extends \Zotlabs\Web\Controller { } if($tag) - $o .= '<h2>' . sprintf( t('Items tagged with: %s'),htmlspecialchars($search, ENT_COMPAT,'UTF-8')) . '</h2>'; + $o .= '<h2>' . sprintf( t('Items tagged with: %s'),$search) . '</h2>'; else - $o .= '<h2>' . sprintf( t('Search results for: %s'),htmlspecialchars($search, ENT_COMPAT,'UTF-8')) . '</h2>'; + $o .= '<h2>' . sprintf( t('Search results for: %s'),$search) . '</h2>'; $o .= conversation($items,'search',$update,'client'); diff --git a/Zotlabs/Update/_1225.php b/Zotlabs/Update/_1225.php new file mode 100644 index 000000000..a7d866154 --- /dev/null +++ b/Zotlabs/Update/_1225.php @@ -0,0 +1,26 @@ +<?php + +namespace Zotlabs\Update; + +class _1225 { + + function run() { + + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $r1 = q("ALTER TABLE pconfig ADD updated timestamp NOT NULL DEFAULT '0001-01-01 00:00:00' "); + $r2 = q("create index \"pconfig_updated_idx\" on pconfig (\"updated\")"); + + $r = ($r1 && $r2); + } + else { + $r = q("ALTER TABLE `pconfig` ADD `updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' , + ADD INDEX `pconfig_updated` (`updated`)"); + } + + if($r) + return UPDATE_SUCCESS; + return UPDATE_FAILED; + + } + +} |