diff options
-rw-r--r-- | Zotlabs/Lib/ThreadItem.php | 6 | ||||
-rw-r--r-- | Zotlabs/Lib/ThreadListener.php | 53 | ||||
-rw-r--r-- | Zotlabs/Module/Item.php | 144 | ||||
-rw-r--r-- | Zotlabs/Module/Share.php | 11 | ||||
-rwxr-xr-x | include/items.php | 19 |
5 files changed, 227 insertions, 6 deletions
diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php index ba7217c8f..bdbfc4385 100644 --- a/Zotlabs/Lib/ThreadItem.php +++ b/Zotlabs/Lib/ThreadItem.php @@ -305,7 +305,11 @@ class ThreadItem { } if ($shareable) { - $share = array( t('Repeat This'), t('repeat')); + // This actually turns out not to be possible in some protocol stacks without opening up hundreds of new issues. + // Will allow it only for uri resolvable sources. + if(strpos($item['mid'],'http') === 0) { + $share = array( t('Repeat This'), t('repeat')); + } $embed = array( t('Share This'), t('share')); } diff --git a/Zotlabs/Lib/ThreadListener.php b/Zotlabs/Lib/ThreadListener.php new file mode 100644 index 000000000..ad682aac8 --- /dev/null +++ b/Zotlabs/Lib/ThreadListener.php @@ -0,0 +1,53 @@ +<?php + +namespace Zotlabs\Lib; + +class ThreadListener { + + static public function store($target_id,$portable_id,$ltype = 0) { + $x = self::fetch($target_id,$portable_id,$ltype = 0); + if(! $x) { + $r = q("insert into listeners ( target_id, portable_id, ltype ) values ( '%s', '%s' , %d ) ", + dbesc($target_id), + dbesc($portable_id), + intval($ltype) + ); + } + } + + static public function fetch($target_id,$portable_id,$ltype = 0) { + $x = q("select * from listeners where target_id = '%s' and portable_id = '%s' and ltype = %d limit 1", + dbesc($target_id), + dbesc($portable_id), + intval($ltype) + ); + if($x) { + return $x[0]; + } + return false; + } + + static public function fetch_by_target($target_id,$ltype = 0) { + $x = q("select * from listeners where target_id = '%s' and ltype = %d limit 1", + dbesc($target_id), + intval($ltype) + ); + + return $x; + } + + static public function delete_by_target($target_id, $ltype = 0) { + return q("delete from listeners where target_id = '%s' and ltype = %d", + dbesc($target_id), + intval($ltype) + ); + } + + static public function delete_by_pid($portable_id, $ltype = 0) { + return q("delete from listeners where portable_id = '%s' and ltype = %d", + dbesc($portable_id), + intval($ltype) + ); + } + +}
\ No newline at end of file diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index f19d4907b..843be394f 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -6,6 +6,12 @@ use Zotlabs\Lib\IConfig; use Zotlabs\Lib\Enotify; use Zotlabs\Web\Controller; use Zotlabs\Daemon\Master; +use Zotlabs\Lib\Activity; +use Zotlabs\Lib\ActivityStreams; +use Zotlabs\Lib\LDSignatures; +use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Lib\Libzot; +use Zotlabs\Lib\ThreadListener; require_once('include/crypto.php'); require_once('include/items.php'); @@ -30,6 +36,144 @@ require_once('include/security.php'); class Item extends Controller { + + function init() { + + if(Libzot::is_zot_request()) { + + $conversation = false; + + $item_id = argv(1); + + if(! $item_id) + http_status_exit(404, 'Not found'); + + + $portable_id = EMPTY_STR; + + $sigdata = HTTPSig::verify(EMPTY_STR); + if($sigdata['portable_id'] && $sigdata['header_valid']) { + $portable_id = $sigdata['portable_id']; + } + + $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 "; + + $sql_extra = item_permissions_sql(0); + + $r = q("select * from item where mid = '%s' $item_normal $sql_extra limit 1", + dbesc(z_root() . '/item/' . $item_id) + ); + if(! $r) { + + + $r = q("select * from item where mid = '%s' $item_normal limit 1", + dbesc(z_root() . '/item/' . $item_id) + ); + if($r) { + http_status_exit(403, 'Forbidden'); + } + http_status_exit(404, 'Not found'); + } + + + $items = q("select parent as item_id from item where mid = '%s' and uid = %d $item_normal $sql_extra ", + dbesc($r[0]['parent_mid']), + intval($r[0]['uid']) + ); + if(! $items) { + http_status_exit(404, 'Not found'); + } + + $r = $items; + + $parents_str = ids_to_querystr($r,'item_id'); + + $items = q("SELECT item.*, item.id AS item_id FROM item WHERE item.parent IN ( %s ) $item_normal $sql_extra ", + dbesc($parents_str) + ); + + if(! $items) { + http_status_exit(404, 'Not found'); + } + + $r = $items; + xchan_query($r,true); + $items = fetch_post_tags($r,true); + + $observer = App::get_observer(); + $parent = $items[0]; + $recips = (($parent['owner']['xchan_network'] === 'activitypub') ? get_iconfig($parent['id'],'activitypub','recips', []) : []); + $to = (($recips && array_key_exists('to',$recips) && is_array($recips['to'])) ? $recips['to'] : null); + $nitems = []; + foreach($items as $i) { + + $mids = []; + + if(intval($i['item_private'])) { + if(! $observer) { + continue; + } + // ignore private reshare, possibly from hubzilla + if($i['verb'] === 'Announce') { + if(! in_array($i['thr_parent'],$mids)) { + $mids[] = $i['thr_parent']; + } + continue; + } + // also ignore any children of the private reshares + if(in_array($i['thr_parent'],$mids)) { + continue; + } + + if((! $to) || (! in_array($observer['xchan_url'],$to))) { + continue; + } + + } + $nitems[] = $i; + } + + if(! $nitems) + http_status_exit(404, 'Not found'); + + $chan = channelx_by_n($nitems[0]['uid']); + + if(! $chan) + http_status_exit(404, 'Not found'); + + if(! perm_is_allowed($chan['channel_id'],get_observer_hash(),'view_stream')) + http_status_exit(403, 'Forbidden'); + + $i = Activity::encode_item_collection($nitems,'conversation/' . $item_id,'OrderedCollection',( defined('NOMADIC') ? false : true)); + if($portable_id) { + ThreadListener::store(z_root() . '/item/' . $item_id,$portable_id); + } + + if(! $i) + http_status_exit(404, 'Not found'); + + $x = array_merge(['@context' => [ + ACTIVITYSTREAMS_JSONLD_REV, + 'https://w3id.org/security/v1', + z_root() . ZOT_APSCHEMA_REV + ]], $i); + + $headers = []; + $headers['Content-Type'] = 'application/x-zot+json' ; + $x['signature'] = LDSignatures::sign($x,$chan); + $ret = json_encode($x, JSON_UNESCAPED_SLASHES); + $headers['Digest'] = HTTPSig::generate_digest_header($ret); + $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; + $h = HTTPSig::create_sig($headers,$chan['channel_prvkey'],channel_url($chan)); + HTTPSig::set_headers($h); + echo $ret; + killme(); + + } + } + + + function post() { // This will change. Figure out who the observer is and whether or not diff --git a/Zotlabs/Module/Share.php b/Zotlabs/Module/Share.php index 58b70318d..53a06b072 100644 --- a/Zotlabs/Module/Share.php +++ b/Zotlabs/Module/Share.php @@ -27,13 +27,15 @@ class Share extends \Zotlabs\Web\Controller { $channel = App::get_channel(); - $r = q("SELECT * from item left join xchan on author_xchan = xchan_hash WHERE id = %d LIMIT 1", intval($post_id) ); if(! $r) killme(); + + + if(($r[0]['item_private']) && ($r[0]['xchan_network'] !== 'rss')) killme(); @@ -49,8 +51,7 @@ class Share extends \Zotlabs\Web\Controller { if($r[0]['mimetype'] !== 'text/bbcode') killme(); - - + xchan_query($r); $arr = []; @@ -94,8 +95,8 @@ class Share extends \Zotlabs\Web\Controller { $arr['item_origin'] = 1; $arr['item_wall'] = $item['item_wall']; - $arr['mid'] = item_message_id(); - $arr['mid'] = str_replace('/item/','/activity/',$arr['mid']); + $arr['uuid'] = item_message_id(); + $arr['mid'] = z_root() . '/activity/' . $arr['uuid']; $arr['parent_mid'] = $item['mid']; $mention = '@[zrl=' . $item['author']['xchan_url'] . ']' . $item['author']['xchan_name'] . '[/zrl]'; diff --git a/include/items.php b/include/items.php index 76ae79e6f..a14e3db3a 100755 --- a/include/items.php +++ b/include/items.php @@ -7,6 +7,7 @@ use Zotlabs\Lib\Enotify; use Zotlabs\Lib\MarkdownSoap; use Zotlabs\Lib\MessageFilter; +use Zotlabs\Lib\ThreadListener; use Zotlabs\Lib\IConfig; use Zotlabs\Access\PermissionLimits; use Zotlabs\Access\AccessList; @@ -141,6 +142,22 @@ function collect_recipients($item, &$private_envelope,$include_groups = true) { // $recipients[] = $sys['xchan_hash']; } + + // Forward to thread listeners, *unless* there is even a remote hint that the item + // might have some privacy attached. This could be (for instance) an ActivityPub DM + // in the middle of a public thread. Unless we can guarantee beyond all doubt that + // this is public, don't allow it to go to thread listeners. + + if(! intval($item['item_private'])) { + $r = ThreadListener::fetch_by_target($item['parent_mid']); + if($r) { + foreach($r as $rv) { + $recipients[] = $rv['portable_id']; + } + } + } + + // Add the authors of any posts in this thread, if they are known to us. // This is specifically designed to forward wall-to-wall posts to the original author, // in case they aren't a connection but have permission to write on our wall. @@ -3867,6 +3884,8 @@ function delete_item_lowlevel($item, $stage = DROPITEM_NORMAL, $force = false) { intval(TERM_OBJ_POST) ); + ThreadListener::delete_by_target($item['mid']); + /** @FIXME remove notifications for this item */ return true; |