diff options
46 files changed, 807 insertions, 824 deletions
diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index 0808fe33f..8168e7354 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -3,7 +3,7 @@ namespace Zotlabs\Lib; use Zotlabs\Daemon\Master; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; require_once('include/event.php'); @@ -312,6 +312,10 @@ class Activity { } } + if (intval($i['item_private']) === 2) { + $ret['directMessage'] = true; + } + $ret['attributedTo'] = $i['author']['xchan_url']; if($i['id'] != $i['parent']) { @@ -483,6 +487,19 @@ class Activity { $ret['type'] = self::activity_mapper($i['verb']); + if($ret['type'] === 'emojiReaction') { + // There may not be an object for these items for legacy reasons - it should be the conversation parent. + $p = q("select * from item where mid = '%s' and uid = %d", + dbesc($i['parent_mid']), + intval($i['uid']) + ); + if($p) { + xchan_query($p,true); + $p = fetch_post_tags($p,true); + $i['obj'] = self::encode_item($p[0]); + } + } + $ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/activity/' . urlencode($i['mid'])); @@ -1415,6 +1432,11 @@ class Activity { if($act->recips && (! in_array(ACTIVITY_PUBLIC_INBOX,$act->recips))) $s['item_private'] = 1; + + if (array_key_exists('directMessage',$act->obj) && intval($act->obj['directMessage'])) { + $s['item_private'] = 2; + } + set_iconfig($s,'activitypub','recips',$act->raw_recips); if($parent) { set_iconfig($s,'activitypub','rawmsg',$act->raw,1); @@ -1835,7 +1857,8 @@ class Activity { $s['item_private'] = 1; set_iconfig($s,'activitypub','recips',$act->raw_recips); - // @FIXME: $parent is not defined + + $parent = (($s['parent_mid'] && $s['parent_mid'] === $s['mid']) ? true : false); if($parent) { set_iconfig($s,'activitypub','rawmsg',$act->raw,1); } @@ -1844,6 +1867,265 @@ class Activity { } + static function store($channel,$observer_hash,$act,$item,$fetch_parents = true) { + + $is_sys_channel = is_sys_channel($channel['channel_id']); + + // Mastodon only allows visibility in public timelines if the public inbox is listed in the 'to' field. + // They are hidden in the public timeline if the public inbox is listed in the 'cc' field. + // This is not part of the activitypub protocol - we might change this to show all public posts in pubstream at some point. + + $pubstream = ((is_array($act->obj) && array_key_exists('to', $act->obj) && in_array(ACTIVITY_PUBLIC_INBOX, $act->obj['to'])) ? true : false); + $is_parent = (($item['parent_mid'] && $item['parent_mid'] === $item['mid']) ? true : false); + + if($is_parent && (! perm_is_allowed($channel['channel_id'],$observer_hash,'send_stream') && ! ($is_sys_channel && $pubstream))) { + logger('no permission'); + return; + } + + if(is_array($act->obj)) { + $content = self::get_content($act->obj); + } + if(! $content) { + logger('no content'); + return; + } + + $item['aid'] = $channel['channel_account_id']; + $item['uid'] = $channel['channel_id']; + $s['uuid'] = ''; + + // Friendica sends the diaspora guid in a nonstandard field via AP + if($act->obj['diaspora:guid']) + $s['uuid'] = $act->obj['diaspora:guid']; + + if(! ( $item['author_xchan'] && $item['owner_xchan'])) { + logger('owner or author missing.'); + return; + } + + if($channel['channel_system']) { + if(! MessageFilter::evaluate($item,get_config('system','pubstream_incl'),get_config('system','pubstream_excl'))) { + logger('post is filtered'); + return; + } + } + + $abook = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", + dbesc($observer_hash), + intval($channel['channel_id']) + ); + + if($abook) { + if(! post_is_importable($item,$abook[0])) { + logger('post is filtered'); + return; + } + } + + + if($act->obj['conversation']) { + set_iconfig($item,'ostatus','conversation',$act->obj['conversation'],1); + } + + // This isn't perfect but the best we can do for now. + + $item['comment_policy'] = 'authenticated'; + + set_iconfig($item,'activitypub','recips',$act->raw_recips); + + if(! $is_parent) { + $p = q("select parent_mid from item where mid = '%s' and uid = %d limit 1", + dbesc($item['parent_mid']), + intval($item['uid']) + ); + if(! $p) { + $a = (($fetch_parents) ? self::fetch_and_store_parents($channel,$act,$item) : false); + if($a) { + $p = q("select parent_mid from item where mid = '%s' and uid = %d limit 1", + dbesc($item['parent_mid']), + intval($item['uid']) + ); + } + else { + logger('could not fetch parents'); + return; + + // @TODO we maybe could accept these is we formatted the body correctly with share_bb() + // or at least provided a link to the object + // if(in_array($act->type,[ 'Like','Dislike' ])) { + // return; + // } + + // @TODO do we actually want that? + // if no parent was fetched, turn into a top-level post + + // turn into a top level post + // $s['parent_mid'] = $s['mid']; + // $s['thr_parent'] = $s['mid']; + } + } + if($p[0]['parent_mid'] !== $item['parent_mid']) { + $item['thr_parent'] = $item['parent_mid']; + } + else { + $item['thr_parent'] = $p[0]['parent_mid']; + } + $item['parent_mid'] = $p[0]['parent_mid']; + } + + $r = q("select id, created, edited from item where mid = '%s' and uid = %d limit 1", + dbesc($item['mid']), + intval($item['uid']) + ); + if($r) { + if($item['edited'] > $r[0]['edited']) { + $item['id'] = $r[0]['id']; + $x = item_store_update($item); + } + else { + return; + } + } + else { + $x = item_store($item); + } + + if(is_array($x) && $x['item_id']) { + if($is_parent) { + if($item['owner_xchan'] === $channel['channel_hash']) { + // We are the owner of this conversation, so send all received comments back downstream + Master::Summon(array('Notifier','comment-import',$x['item_id'])); + } + $r = q("select * from item where id = %d limit 1", + intval($x['item_id']) + ); + if($r) { + send_status_notifications($x['item_id'],$r[0]); + } + } + sync_an_item($channel['channel_id'],$x['item_id']); + } + + } + + static public function fetch_and_store_parents($channel,$act,$item) { + + logger('fetching parents'); + + $p = []; + + $current_act = $act; + $current_item = $item; + + while($current_item['parent_mid'] !== $current_item['mid']) { + $n = ActivityStreams::fetch($current_item['parent_mid'], $channel); + if(! $n) { + break; + } + $a = new ActivityStreams($n); + + logger($a->debug()); + + if(! $a->is_valid()) { + break; + } + + $replies = null; + if(isset($a->obj['replies']['first']['items'])) { + $replies = $a->obj['replies']['first']['items']; + // we already have this one + array_diff($replies, [$current_item['mid']]); + } + + $item = null; + + switch($a->type) { + case 'Create': + case 'Update': + case 'Like': + case 'Dislike': + case 'Announce': + $item = self::decode_note($a); + break; + default: + break; + + } + if(! $item) { + break; + } + + array_unshift($p,[ $a, $item, $replies]); + + if($item['parent_mid'] === $item['mid'] || count($p) > 20) { + break; + } + + $current_act = $a; + $current_item = $item; + } + + if($p) { + foreach($p as $pv) { + self::store($channel,$pv[0]->actor['id'],$pv[0],$pv[1],false); + if($pv[2]) + self::fetch_and_store_replies($channel, $pv[2]); + } + return true; + } + + return false; + } + + static public function fetch_and_store_replies($channel, $arr) { + + logger('fetching replies'); + + $p = []; + + foreach($arr as $url) { + + $n = ActivityStreams::fetch($url, $channel); + if(! $n) { + break; + } + + $a = new ActivityStreams($n); + + if(! $a->is_valid()) { + break; + } + + $item = null; + + switch($a->type) { + case 'Create': + case 'Update': + case 'Like': + case 'Dislike': + case 'Announce': + $item = self::decode_note($a); + break; + default: + break; + } + if(! $item) { + break; + } + + array_unshift($p,[ $a, $item ]); + + } + + if($p) { + foreach($p as $pv) { + self::store($channel,$pv[0]->actor['id'],$pv[0],$pv[1],false); + } + } + + } + static function announce_note($channel,$observer_hash,$act) { $s = []; @@ -1964,25 +2246,21 @@ class Activity { $x = item_store($s); } - if(is_array($x) && $x['item_id']) { - // @FIXME: $parent is not defined - if($parent) { - if($s['owner_xchan'] === $channel['channel_hash']) { - // We are the owner of this conversation, so send all received comments back downstream - Master::Summon(array('Notifier','comment-import',$x['item_id'])); - } - $r = q("select * from item where id = %d limit 1", - intval($x['item_id']) - ); - if($r) { - send_status_notifications($x['item_id'],$r[0]); - } + if($s['owner_xchan'] === $channel['channel_hash']) { + // We are the owner of this conversation, so send all received comments back downstream + Master::Summon(array('Notifier','comment-import',$x['item_id'])); } + $r = q("select * from item where id = %d limit 1", + intval($x['item_id']) + ); + if($r) { + send_status_notifications($x['item_id'],$r[0]); + } + sync_an_item($channel['channel_id'],$x['item_id']); } - } static function like_note($channel,$observer_hash,$act) { diff --git a/Zotlabs/Lib/JSalmon.php b/Zotlabs/Lib/JSalmon.php index f35bf6235..bed748432 100644 --- a/Zotlabs/Lib/JSalmon.php +++ b/Zotlabs/Lib/JSalmon.php @@ -2,7 +2,7 @@ namespace Zotlabs\Lib; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; class JSalmon { diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php index 0ad8afc94..2a13744a3 100644 --- a/Zotlabs/Lib/Libzot.php +++ b/Zotlabs/Lib/Libzot.php @@ -2,7 +2,7 @@ namespace Zotlabs\Lib; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; use Zotlabs\Access\Permissions; use Zotlabs\Access\PermissionLimits; use Zotlabs\Daemon\Master; diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php index 9161aa182..5e4600df2 100644 --- a/Zotlabs/Lib/ThreadItem.php +++ b/Zotlabs/Lib/ThreadItem.php @@ -98,7 +98,7 @@ class ThreadItem { $conv = $this->get_conversation(); $observer = $conv->get_observer(); - $lock = ((($item['item_private'] == 1) || (($item['uid'] == local_channel()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) + $lock = (((intval($item['item_private'])) || (($item['uid'] == local_channel()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])))) ? t('Private Message') : false); @@ -110,7 +110,7 @@ class ThreadItem { $shareable = true; $privacy_warning = false; - if(($item['item_private'] == 1) && ($item['owner']['xchan_network'] === 'activitypub')) { + if(intval($item['item_private']) && ($item['owner']['xchan_network'] === 'activitypub')) { $recips = get_iconfig($item['parent'], 'activitypub', 'recips'); if(! in_array($observer['xchan_url'], $recips['to'])) diff --git a/Zotlabs/Lib/ZotURL.php b/Zotlabs/Lib/ZotURL.php index bc14c516a..98d1febe5 100644 --- a/Zotlabs/Lib/ZotURL.php +++ b/Zotlabs/Lib/ZotURL.php @@ -2,7 +2,7 @@ namespace Zotlabs\Lib; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; class ZotURL { diff --git a/Zotlabs/Lib/Zotfinger.php b/Zotlabs/Lib/Zotfinger.php index d094fdc8d..2d2e6796b 100644 --- a/Zotlabs/Lib/Zotfinger.php +++ b/Zotlabs/Lib/Zotfinger.php @@ -2,7 +2,7 @@ namespace Zotlabs\Lib; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; class Zotfinger { diff --git a/Zotlabs/Module/Apschema.php b/Zotlabs/Module/Apschema.php index d217041f2..12cc0e00a 100644 --- a/Zotlabs/Module/Apschema.php +++ b/Zotlabs/Module/Apschema.php @@ -28,7 +28,8 @@ class Apschema extends \Zotlabs\Web\Controller { 'nomadicHubs' => 'zot:nomadicHubs', 'emojiReaction' => 'zot:emojiReaction', 'expires' => 'zot:expires', - + 'directMessage' => 'zot:directMessage', + 'magicEnv' => [ '@id' => 'zot:magicEnv', '@type' => '@id' @@ -40,8 +41,11 @@ class Apschema extends \Zotlabs\Web\Controller { ], 'ostatus' => 'http://ostatus.org#', - 'conversation' => 'ostatus:conversation' + 'conversation' => 'ostatus:conversation', + 'diaspora' => 'https://diasporafoundation.org/ns/', + 'guid' => 'diaspora:guid' + ] ]; diff --git a/Zotlabs/Module/Cal.php b/Zotlabs/Module/Cal.php index a84116e76..07bee38bd 100644 --- a/Zotlabs/Module/Cal.php +++ b/Zotlabs/Module/Cal.php @@ -161,12 +161,12 @@ class Cal extends Controller { 'end' => $end, 'drop' => $drop, 'allDay' => (($rr['adjust']) ? 0 : 1), - 'title' => htmlentities($rr['summary'], ENT_COMPAT, 'UTF-8', false), + 'title' => html_entity_decode($rr['summary'], ENT_COMPAT, 'UTF-8'), 'editable' => $edit ? true : false, 'item' => $rr, 'plink' => [$rr['plink'], t('Link to source')], - 'description' => htmlentities($rr['description'], ENT_COMPAT, 'UTF-8', false), - 'location' => htmlentities($rr['location'], ENT_COMPAT, 'UTF-8', false), + 'description' => html_entity_decode($rr['description'], ENT_COMPAT, 'UTF-8'), + 'location' => html_entity_decode($rr['location'], ENT_COMPAT, 'UTF-8'), 'allow_cid' => expand_acl($rr['allow_cid']), 'allow_gid' => expand_acl($rr['allow_gid']), 'deny_cid' => expand_acl($rr['deny_cid']), diff --git a/Zotlabs/Module/Cdav.php b/Zotlabs/Module/Cdav.php index de639e281..e2855d2b6 100644 --- a/Zotlabs/Module/Cdav.php +++ b/Zotlabs/Module/Cdav.php @@ -4,6 +4,7 @@ namespace Zotlabs\Module; use App; use Zotlabs\Lib\Apps; use Zotlabs\Web\Controller; +use Zotlabs\Web\HTTPSig; require_once('include/event.php'); @@ -41,7 +42,7 @@ class Cdav extends Controller { continue; } - $sigblock = \Zotlabs\Web\HTTPSig::parse_sigheader($_SERVER[$head]); + $sigblock = HTTPSig::parse_sigheader($_SERVER[$head]); if($sigblock) { $keyId = str_replace('acct:','',$sigblock['keyId']); if($keyId) { @@ -64,7 +65,7 @@ class Cdav extends Controller { continue; if($record) { - $verified = \Zotlabs\Web\HTTPSig::verify('',$record['channel']['channel_pubkey']); + $verified = HTTPSig::verify('',$record['channel']['channel_pubkey']); if(! ($verified && $verified['header_signed'] && $verified['header_valid'])) { $record = null; } @@ -277,11 +278,11 @@ class Cdav extends Controller { $allday = $_REQUEST['allday']; $title = $_REQUEST['title']; - $start = datetime_convert($tz, 'UTC', $_REQUEST['dtstart']); + $start = datetime_convert('UTC', 'UTC', $_REQUEST['dtstart']); $dtstart = new \DateTime($start); if($_REQUEST['dtend']) { - $end = datetime_convert($tz, 'UTC', $_REQUEST['dtend']); + $end = datetime_convert('UTC', 'UTC', $_REQUEST['dtend']); $dtend = new \DateTime($end); } $description = $_REQUEST['description']; @@ -368,10 +369,10 @@ class Cdav extends Controller { $uri = $_REQUEST['uri']; $title = $_REQUEST['title']; - $start = datetime_convert($tz, 'UTC', $_REQUEST['dtstart']); + $start = datetime_convert('UTC', 'UTC', $_REQUEST['dtstart']); $dtstart = new \DateTime($start); if($_REQUEST['dtend']) { - $end = datetime_convert($tz, 'UTC', $_REQUEST['dtend']); + $end = datetime_convert('UTC', 'UTC', $_REQUEST['dtend']); $dtend = new \DateTime($end); } $description = $_REQUEST['description']; @@ -441,10 +442,10 @@ class Cdav extends Controller { $allday = $_REQUEST['allday']; $uri = $_REQUEST['uri']; - $start = datetime_convert($tz, 'UTC', $_REQUEST['dtstart']); + $start = datetime_convert('UTC', 'UTC', $_REQUEST['dtstart']); $dtstart = new \DateTime($start); if($_REQUEST['dtend']) { - $end = datetime_convert($tz, 'UTC', $_REQUEST['dtend']); + $end = datetime_convert('UTC', 'UTC', $_REQUEST['dtend']); $dtend = new \DateTime($end); } diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php index 144c2472a..b1639b213 100644 --- a/Zotlabs/Module/Channel.php +++ b/Zotlabs/Module/Channel.php @@ -6,7 +6,7 @@ namespace Zotlabs\Module; use App; use Zotlabs\Web\Controller; use Zotlabs\Lib\PermissionDescription; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; use Zotlabs\Lib\Libzot; require_once('include/items.php'); @@ -111,6 +111,17 @@ class Channel extends Controller { // we start loading content profile_load($which,$profile); + + App::$page['htmlhead'] .= '<meta property="og:title" content="' . htmlspecialchars($channel['channel_name']) . '">' . "\r\n"; + App::$page['htmlhead'] .= '<meta property="og:image" content="' . $channel['xchan_photo_l'] . '">' . "\r\n"; + + if(App::$profile['about'] && perm_is_allowed($channel['channel_id'],get_observer_hash(),'view_profile')) { + App::$page['htmlhead'] .= '<meta property="og:description" content="' . htmlspecialchars(App::$profile['about']) . '">' . "\r\n"; + } + else { + App::$page['htmlhead'] .= '<meta property="og:description" content="' . htmlspecialchars(sprintf( t('This is the home page of %s.'), $channel['channel_name'])) . '">' . "\r\n"; + } + } function get($update = 0, $load = false) { diff --git a/Zotlabs/Module/Channel_calendar.php b/Zotlabs/Module/Channel_calendar.php index ac08dfa96..7d75a7e41 100644 --- a/Zotlabs/Module/Channel_calendar.php +++ b/Zotlabs/Module/Channel_calendar.php @@ -21,7 +21,7 @@ class Channel_calendar extends \Zotlabs\Web\Controller { $event_hash = ((x($_POST,'event_hash')) ? $_POST['event_hash'] : ''); $xchan = ((x($_POST,'xchan')) ? dbesc($_POST['xchan']) : ''); - $uid = local_channel(); + $uid = local_channel(); // only allow editing your own events. if(($xchan) && ($xchan !== get_observer_hash())) @@ -34,8 +34,8 @@ class Channel_calendar extends \Zotlabs\Web\Controller { $adjust = intval($_POST['adjust']); - $start = (($adjust) ? datetime_convert($tz, 'UTC', escape_tags($_REQUEST['dtstart'])) : datetime_convert('UTC', 'UTC', escape_tags($_REQUEST['dtstart']))); - $finish = (($adjust) ? datetime_convert($tz, 'UTC', escape_tags($_REQUEST['dtend'])) : datetime_convert('UTC', 'UTC', escape_tags($_REQUEST['dtend']))); + $start = datetime_convert('UTC', 'UTC', escape_tags($_REQUEST['dtstart'])); + $finish = datetime_convert('UTC', 'UTC', escape_tags($_REQUEST['dtend'])); $summary = escape_tags(trim($_POST['summary'])); $desc = escape_tags(trim($_POST['desc'])); @@ -381,12 +381,12 @@ class Channel_calendar extends \Zotlabs\Web\Controller { 'end' => $end, 'drop' => $drop, 'allDay' => (($rr['adjust']) ? 0 : 1), - 'title' => htmlentities($rr['summary'], ENT_COMPAT, 'UTF-8', false), + 'title' => html_entity_decode($rr['summary'], ENT_COMPAT, 'UTF-8'), 'editable' => $edit ? true : false, 'item' => $rr, 'plink' => [$rr['plink'], t('Link to source')], - 'description' => htmlentities($rr['description'], ENT_COMPAT, 'UTF-8', false), - 'location' => htmlentities($rr['location'], ENT_COMPAT, 'UTF-8', false), + 'description' => html_entity_decode($rr['description'], ENT_COMPAT, 'UTF-8'), + 'location' => html_entity_decode($rr['location'], ENT_COMPAT, 'UTF-8'), 'allow_cid' => expand_acl($rr['allow_cid']), 'allow_gid' => expand_acl($rr['allow_gid']), 'deny_cid' => expand_acl($rr['deny_cid']), @@ -402,7 +402,7 @@ class Channel_calendar extends \Zotlabs\Web\Controller { echo ical_wrapper($r); killme(); } - + if (\App::$argv[1] === 'json'){ json_return_and_die($events); } diff --git a/Zotlabs/Module/Dav.php b/Zotlabs/Module/Dav.php index 9f64e2fea..866520461 100644 --- a/Zotlabs/Module/Dav.php +++ b/Zotlabs/Module/Dav.php @@ -8,8 +8,9 @@ namespace Zotlabs\Module; -use \Sabre\DAV as SDAV; -use \Zotlabs\Storage; +use Sabre\DAV as SDAV; +use Zotlabs\Storage; +use Zotlabs\Web\HTTPSig; require_once('include/attach.php'); require_once('include/auth.php'); @@ -46,7 +47,7 @@ class Dav extends \Zotlabs\Web\Controller { continue; } - $sigblock = \Zotlabs\Web\HTTPSig::parse_sigheader($_SERVER[$head]); + $sigblock = HTTPSig::parse_sigheader($_SERVER[$head]); if($sigblock) { $keyId = str_replace('acct:','',$sigblock['keyId']); if($keyId) { @@ -69,7 +70,7 @@ class Dav extends \Zotlabs\Web\Controller { continue; if($record) { - $verified = \Zotlabs\Web\HTTPSig::verify('',$record['channel']['channel_pubkey']); + $verified = HTTPSig::verify('',$record['channel']['channel_pubkey']); if(! ($verified && $verified['header_signed'] && $verified['header_valid'])) { $record = null; } diff --git a/Zotlabs/Module/Dirsearch.php b/Zotlabs/Module/Dirsearch.php index 26cb82044..92b33df0c 100644 --- a/Zotlabs/Module/Dirsearch.php +++ b/Zotlabs/Module/Dirsearch.php @@ -394,7 +394,7 @@ class Dirsearch extends \Zotlabs\Web\Controller { $quoted_string = false; } else - $curr['value'] .= ' ' . trim(q); + $curr['value'] .= ' ' . trim($q); } } } diff --git a/Zotlabs/Module/Events.php b/Zotlabs/Module/Events.php index dcdbfd233..681d6887d 100644 --- a/Zotlabs/Module/Events.php +++ b/Zotlabs/Module/Events.php @@ -668,9 +668,10 @@ class Events extends \Zotlabs\Web\Controller { 'html'=>$html, 'plink' => array($rr['plink'],t('Link to Source'),'',''), ); + } } - + if($export) { header('Content-type: text/calendar'); header('content-disposition: attachment; filename="' . t('calendar') . '-' . $channel['channel_address'] . '.ics"' ); diff --git a/Zotlabs/Module/Getfile.php b/Zotlabs/Module/Getfile.php index 583cf38f0..6d31d23fd 100644 --- a/Zotlabs/Module/Getfile.php +++ b/Zotlabs/Module/Getfile.php @@ -1,6 +1,8 @@ <?php namespace Zotlabs\Module; +use Zotlabs\Web\HTTPSig; + /** * module: getfile * @@ -46,7 +48,7 @@ class Getfile extends \Zotlabs\Web\Controller { continue; } - $sigblock = \Zotlabs\Web\HTTPSig::parse_sigheader($_SERVER[$head]); + $sigblock = HTTPSig::parse_sigheader($_SERVER[$head]); if($sigblock) { $keyId = $sigblock['keyId']; @@ -57,7 +59,7 @@ class Getfile extends \Zotlabs\Web\Controller { ); if($r) { $hubloc = $r[0]; - $verified = \Zotlabs\Web\HTTPSig::verify('',$hubloc['xchan_pubkey']); + $verified = HTTPSig::verify('',$hubloc['xchan_pubkey']); if($verified && $verified['header_signed'] && $verified['header_valid'] && $hash == $hubloc['hubloc_hash']) { $header_verified = true; } diff --git a/Zotlabs/Module/Id.php b/Zotlabs/Module/Id.php index 15abfa2a3..e08568d00 100644 --- a/Zotlabs/Module/Id.php +++ b/Zotlabs/Module/Id.php @@ -12,7 +12,7 @@ namespace Zotlabs\Module; use Zotlabs\Lib\Activity; use Zotlabs\Lib\ActivityStreams; use Zotlabs\Lib\LDSignatures; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; use Zotlabs\Web\Controller; use Zotlabs\Lib\Libzot; use Zotlabs\Lib\ThreadListener; diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index 965cbf173..d03b6ee30 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -9,7 +9,7 @@ use Zotlabs\Daemon\Master; use Zotlabs\Lib\Activity; use Zotlabs\Lib\ActivityStreams; use Zotlabs\Lib\LDSignatures; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; use Zotlabs\Lib\Libzot; use Zotlabs\Lib\ThreadListener; use App; @@ -193,6 +193,25 @@ class Item extends Controller { killme(); } + + if(argc() > 1 && argv(1) !== 'drop') { + $x = q("select uid, item_wall, llink, mid from item where mid = '%s' ", + dbesc(z_root() . '/item/' . argv(1)) + ); + if($x) { + foreach($x as $xv) { + if (intval($xv['item_wall'])) { + $c = channelx_by_n($xv['uid']); + if ($c) { + goaway($c['xchan_url'] . '?mid=' . gen_link_id($xv['mid'])); + } + } + } + goaway($x[0]['llink']); + } + http_status_exit(404, 'Not found'); + } + } @@ -551,10 +570,10 @@ class Item extends Controller { $public_policy = $orig_post['public_policy']; $private = $orig_post['item_private']; } - - if($private || $public_policy || $acl->is_private()) - $private = 1; - + + if($public_policy || $acl->is_private()) { + $private = (($private) ? $private : 1); + } $location = $orig_post['location']; $coord = $orig_post['coord']; @@ -631,12 +650,11 @@ class Item extends Controller { $allow_empty = ((array_key_exists('allow_empty',$_REQUEST)) ? intval($_REQUEST['allow_empty']) : 0); - $private = intval($acl->is_private() || ($public_policy)); + $private = (($private) ? $private : intval($acl->is_private() || ($public_policy))); // If this is a comment, set the permissions from the parent. if($parent_item) { - $private = 0; $acl->set($parent_item); $private = intval($acl->is_private() || $parent_item['item_private']); $public_policy = $parent_item['public_policy']; @@ -742,7 +760,12 @@ class Item extends Controller { } } } - + + if(($str_contact_allow) && (! $str_group_allow)) { + // direct message - private between individual channels but not groups + $private = 2; + } + /** * diff --git a/Zotlabs/Module/Lockview.php b/Zotlabs/Module/Lockview.php index d7ed07a53..8c8519c57 100644 --- a/Zotlabs/Module/Lockview.php +++ b/Zotlabs/Module/Lockview.php @@ -76,7 +76,7 @@ class Lockview extends \Zotlabs\Web\Controller { killme(); } - if(($item['item_private'] == 1) && (! strlen($item['allow_cid'])) && (! strlen($item['allow_gid'])) + if(intval($item['item_private']) && (! strlen($item['allow_cid'])) && (! strlen($item['allow_gid'])) && (! strlen($item['deny_cid'])) && (! strlen($item['deny_gid']))) { // if the post is private, but public_policy is blank ("visible to the internet"), and there aren't any diff --git a/Zotlabs/Module/Magic.php b/Zotlabs/Module/Magic.php index e8e960574..6ac656a04 100644 --- a/Zotlabs/Module/Magic.php +++ b/Zotlabs/Module/Magic.php @@ -1,6 +1,8 @@ <?php namespace Zotlabs\Module; +use Zotlabs\Web\HTTPSig; + @require_once('include/zot.php'); @@ -152,10 +154,9 @@ class Magic extends \Zotlabs\Web\Controller { $headers['Accept'] = 'application/x-zot+json' ; $headers['X-Open-Web-Auth'] = random_string(); $headers['Host'] = $parsed['host']; - $headers['Digest'] = 'SHA-256=' . \Zotlabs\Web\HTTPSig::generate_digest($data,false); + $headers['Digest'] = HTTPSig::generate_digest_header($data); - $headers = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], - 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,true,'sha512'); + $headers = HTTPSig::create_sig($headers,$channel['channel_prvkey'], 'acct:' . channel_reddress($channel),true,'sha512'); $x = z_post_url($basepath . '/owa',$data,$redirects,[ 'headers' => $headers ]); if($x['success']) { diff --git a/Zotlabs/Module/Owa.php b/Zotlabs/Module/Owa.php index cf116a96c..89f83bf8f 100644 --- a/Zotlabs/Module/Owa.php +++ b/Zotlabs/Module/Owa.php @@ -2,6 +2,8 @@ namespace Zotlabs\Module; +use Zotlabs\Web\HTTPSig; + /** * OpenWebAuth verifier and token generator * See https://macgirvin.com/wiki/mike/OpenWebAuth/Home @@ -25,7 +27,7 @@ class Owa extends \Zotlabs\Web\Controller { continue; } - $sigblock = \Zotlabs\Web\HTTPSig::parse_sigheader($_SERVER[$head]); + $sigblock = HTTPSig::parse_sigheader($_SERVER[$head]); if($sigblock) { $keyId = $sigblock['keyId']; @@ -65,7 +67,7 @@ class Owa extends \Zotlabs\Web\Controller { if ($r) { foreach($r as $hubloc) { - $verified = \Zotlabs\Web\HTTPSig::verify(file_get_contents('php://input'),$hubloc['xchan_pubkey']); + $verified = HTTPSig::verify(file_get_contents('php://input'),$hubloc['xchan_pubkey']); if($verified && $verified['header_signed'] && $verified['header_valid']) { logger('OWA header: ' . print_r($verified,true),LOGGER_DATA); logger('OWA success: ' . $hubloc['hubloc_addr'],LOGGER_DATA); diff --git a/Zotlabs/Module/Ping.php b/Zotlabs/Module/Ping.php index 3dabe0f7b..6e8042eaf 100644 --- a/Zotlabs/Module/Ping.php +++ b/Zotlabs/Module/Ping.php @@ -282,8 +282,8 @@ class Ping extends \Zotlabs\Web\Controller { if(strpos($message, $tt['xname']) === 0) $message = substr($message, strlen($tt['xname']) + 1); - $mid = basename($tt['link']); + $mid = ((strpos($mid, 'b64.') === 0) ? @base64url_decode(substr($mid, 4)) : $mid); if(in_array($tt['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) { // we need the thread parent @@ -291,7 +291,6 @@ class Ping extends \Zotlabs\Web\Controller { dbesc($mid), intval(local_channel()) ); - $b64mid = ((strpos($r[0]['thr_parent'], 'b64.') === 0) ? $r[0]['thr_parent'] : 'b64.' . base64url_encode($r[0]['thr_parent'])); } else { diff --git a/Zotlabs/Module/Zfinger.php b/Zotlabs/Module/Zfinger.php index 6ed001df5..3a20144a5 100644 --- a/Zotlabs/Module/Zfinger.php +++ b/Zotlabs/Module/Zfinger.php @@ -1,6 +1,7 @@ <?php namespace Zotlabs\Module; +use Zotlabs\Web\HTTPSig; class Zfinger extends \Zotlabs\Web\Controller { @@ -23,10 +24,9 @@ class Zfinger extends \Zotlabs\Web\Controller { $ret = json_encode($x); if($chan) { - $hash = \Zotlabs\Web\HTTPSig::generate_digest($ret,false); - $headers['Digest'] = 'SHA-256=' . $hash; - \Zotlabs\Web\HTTPSig::create_sig('',$headers,$chan['channel_prvkey'], - 'acct:' . $chan['channel_address'] . '@' . \App::get_hostname(),true); + $headers['Digest'] = HTTPSig::generate_digest_header($ret); + $h = HTTPSig::create_sig($headers,$chan['channel_prvkey'],'acct:' . channel_reddress($chan)); + HTTPSig::set_headers($h); } else { foreach($headers as $k => $v) { diff --git a/Zotlabs/Module/Zot_probe.php b/Zotlabs/Module/Zot_probe.php index d0c7e688f..648ed2175 100644 --- a/Zotlabs/Module/Zot_probe.php +++ b/Zotlabs/Module/Zot_probe.php @@ -3,7 +3,7 @@ namespace Zotlabs\Module; use Zotlabs\Lib\Zotfinger; -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; class Zot_probe extends \Zotlabs\Web\Controller { diff --git a/Zotlabs/Web/HTTPSig.php b/Zotlabs/Web/HTTPSig.php index fe0b9428f..3d050fd9b 100644 --- a/Zotlabs/Web/HTTPSig.php +++ b/Zotlabs/Web/HTTPSig.php @@ -2,11 +2,17 @@ namespace Zotlabs\Web; +use Zotlabs\Lib\ActivityStreams; +use Zotlabs\Lib\Webfinger; +use Zotlabs\Web\HTTPHeaders; +use Zotlabs\Lib\Libzot; + /** - * @brief Implements HTTP Signatures per draft-cavage-http-signatures-07. + * @brief Implements HTTP Signatures per draft-cavage-http-signatures-10. * - * @see https://tools.ietf.org/html/draft-cavage-http-signatures-07 + * @see https://tools.ietf.org/html/draft-cavage-http-signatures-10 */ + class HTTPSig { /** @@ -15,41 +21,32 @@ class HTTPSig { * @see https://tools.ietf.org/html/rfc5843 * * @param string $body The value to create the digest for - * @param boolean $set (optional, default true) - * If set send a Digest HTTP header - * @return string The generated digest of $body + * @param string $alg hash algorithm (one of 'sha256','sha512') + * @return string The generated digest header string for $body */ - static function generate_digest($body, $set = true) { - $digest = base64_encode(hash('sha256', $body, true)); - if($set) { - header('Digest: SHA-256=' . $digest); + static function generate_digest_header($body,$alg = 'sha256') { + + $digest = base64_encode(hash($alg, $body, true)); + switch($alg) { + case 'sha512': + return 'SHA-512=' . $digest; + case 'sha256': + default: + return 'SHA-256=' . $digest; + break; } - return $digest; } - // See draft-cavage-http-signatures-08 - - static function verify($data,$key = '') { - - $body = $data; - $headers = null; - $spoofable = false; - - $result = [ - 'signer' => '', - 'header_signed' => false, - 'header_valid' => false, - 'content_signed' => false, - 'content_valid' => false - ]; + static function find_headers($data,&$body) { // decide if $data arrived via controller submission or curl + if(is_array($data) && $data['header']) { if(! $data['success']) - return $result; + return []; - $h = new \Zotlabs\Web\HTTPHeaders($data['header']); + $h = new HTTPHeaders($data['header']); $headers = $h->fetcharr(); $body = $data['body']; $headers['(request-target)'] = $data['request_target']; @@ -57,9 +54,7 @@ class HTTPSig { else { $headers = []; - $headers['(request-target)'] = - strtolower($_SERVER['REQUEST_METHOD']) . ' ' . - $_SERVER['REQUEST_URI']; + $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; $headers['content-type'] = $_SERVER['CONTENT_TYPE']; $headers['content-length'] = $_SERVER['CONTENT_LENGTH']; @@ -71,9 +66,35 @@ class HTTPSig { } } - // logger('SERVER: ' . print_r($_SERVER,true), LOGGER_ALL); + //logger('SERVER: ' . print_r($_SERVER,true), LOGGER_ALL); + + //logger('headers: ' . print_r($headers,true), LOGGER_ALL); + + return $headers; + } + + + // See draft-cavage-http-signatures-10 + + static function verify($data,$key = '') { + + $body = $data; + $headers = null; + + $result = [ + 'signer' => '', + 'portable_id' => '', + 'header_signed' => false, + 'header_valid' => false, + 'content_signed' => false, + 'content_valid' => false + ]; + + + $headers = self::find_headers($data,$body); - // logger('headers: ' . print_r($headers,true), LOGGER_ALL); + if(! $headers) + return $result; $sig_block = null; @@ -85,7 +106,7 @@ class HTTPSig { } if(! $sig_block) { - logger('no signature provided.'); + logger('no signature provided.', LOGGER_DEBUG); return $result; } @@ -103,9 +124,6 @@ class HTTPSig { if(array_key_exists($h,$headers)) { $signed_data .= $h . ': ' . $headers[$h] . "\n"; } - if(strpos($h,'.')) { - $spoofable = true; - } if($h === 'date') { $d = new \DateTime($headers[$h]); $d->setTimeZone(new \DateTimeZone('UTC')); @@ -128,63 +146,89 @@ class HTTPSig { $algorithm = 'sha512'; } - if($key && function_exists($key)) { - $result['signer'] = $sig_block['keyId']; - $key = $key($sig_block['keyId']); - } + if(! array_key_exists('keyId',$sig_block)) + return $result; - if(! $key) { - $result['signer'] = $sig_block['keyId']; - $key = self::get_activitypub_key($sig_block['keyId']); - } + $result['signer'] = $sig_block['keyId']; - if(! $key) + $key = self::get_key($key,$result['signer']); + + if(! ($key && $key['public_key'])) { return $result; + } - $x = rsa_verify($signed_data,$sig_block['signature'],$key,$algorithm); + $x = rsa_verify($signed_data,$sig_block['signature'],$key['public_key'],$algorithm); logger('verified: ' . $x, LOGGER_DEBUG); - if(! $x) + if(! $x) { + logger('verify failed for ' . $result['signer'] . ' alg=' . $algorithm . (($key['public_key']) ? '' : ' no key')); + $sig_block['signature'] = base64_encode($sig_block['signature']); + logger('affected sigblock: ' . print_r($sig_block,true)); + logger('signed_data: ' . print_r($signed_data,true)); + logger('headers: ' . print_r($headers,true)); + logger('server: ' . print_r($_SERVER,true)); return $result; + } - if(! $spoofable) - $result['header_valid'] = true; + $result['portable_id'] = $key['portable_id']; + $result['header_valid'] = true; if(in_array('digest',$signed_headers)) { $result['content_signed'] = true; - $digest = explode('=', $headers['digest']); + $digest = explode('=', $headers['digest'], 2); if($digest[0] === 'SHA-256') $hashalg = 'sha256'; if($digest[0] === 'SHA-512') $hashalg = 'sha512'; - // The explode operation will have stripped the '=' padding, so compare against unpadded base64 - if(rtrim(base64_encode(hash($hashalg,$body,true)),'=') === $digest[1]) { + if(base64_encode(hash($hashalg,$body,true)) === $digest[1]) { $result['content_valid'] = true; } + + logger('Content_Valid: ' . (($result['content_valid']) ? 'true' : 'false')); } + return $result; + } - if(in_array('x-zot-digest',$signed_headers)) { - $result['content_signed'] = true; - $digest = explode('=', $headers['x-zot-digest']); - if($digest[0] === 'SHA-256') - $hashalg = 'sha256'; - if($digest[0] === 'SHA-512') - $hashalg = 'sha512'; + static function get_key($key,$id) { - // The explode operation will have stripped the '=' padding, so compare against unpadded base64 - if(rtrim(base64_encode(hash($hashalg,$_POST['data'],true)),'=') === $digest[1]) { - $result['content_valid'] = true; + if($key) { + if(function_exists($key)) { + return $key($id); } + return [ 'public_key' => $key ]; } - logger('Content_Valid: ' . (($result['content_valid']) ? 'true' : 'false')); + if(strpos($id,'#') === false) { + $key = self::get_webfinger_key($id); + } + + if(! $key) { + $key = self::get_activitystreams_key($id); + } + + return $key; + + } + + + function convertKey($key) { + + if(strstr($key,'RSA ')) { + return rsatopem($key); + } + elseif(substr($key,0,5) === 'data:') { + return convert_salmon_key($key); + } + else { + return $key; + } - return $result; } + /** * @brief * @@ -192,57 +236,131 @@ class HTTPSig { * @return boolean|string * false if no pub key found, otherwise return the pub key */ - function get_activitypub_key($id) { - if(strpos($id,'acct:') === 0) { - $x = q("select xchan_pubkey from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' limit 1", - dbesc(str_replace('acct:','',$id)) - ); + function get_activitystreams_key($id) { + + // remove fragment + + $url = ((strpos($id,'#')) ? substr($id,0,strpos($id,'#')) : $id); + + $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1", + dbesc(str_replace('acct:','',$url)), + dbesc($url) + ); + + if($x && $x[0]['xchan_pubkey']) { + return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; } - else { - $x = q("select xchan_pubkey from xchan where xchan_hash = '%s' and xchan_network = 'activitypub' ", - dbesc($id) - ); + + $r = ActivityStreams::fetch($id); + + if($r) { + if(array_key_exists('publicKey',$r) && array_key_exists('publicKeyPem',$r['publicKey']) && array_key_exists('id',$r['publicKey'])) { + if($r['publicKey']['id'] === $id || $r['id'] === $id) { + $portable_id = ((array_key_exists('owner',$r['publicKey'])) ? $r['publicKey']['owner'] : EMPTY_STR); + return [ 'public_key' => self::convertKey($r['publicKey']['publicKeyPem']), 'portable_id' => $portable_id, 'hubloc' => [] ]; + } + } } + return false; + } + + + function get_webfinger_key($id) { + + $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1", + dbesc(str_replace('acct:','',$id)), + dbesc($id) + ); if($x && $x[0]['xchan_pubkey']) { - return ($x[0]['xchan_pubkey']); + return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; } - if(function_exists('as_fetch')) - $r = as_fetch($id); + $wf = Webfinger::exec($id); + $key = [ 'portable_id' => '', 'public_key' => '', 'hubloc' => [] ]; - if($r) { - $j = json_decode($r,true); + if($wf) { + if(array_key_exists('properties',$wf) && array_key_exists('https://w3id.org/security/v1#publicKeyPem',$wf['properties'])) { + $key['public_key'] = self::convertKey($wf['properties']['https://w3id.org/security/v1#publicKeyPem']); + } + if(array_key_exists('links', $wf) && is_array($wf['links'])) { + foreach($wf['links'] as $l) { + if(! (is_array($l) && array_key_exists('rel',$l))) { + continue; + } + if($l['rel'] === 'magic-public-key' && array_key_exists('href',$l) && $key['public_key'] === EMPTY_STR) { + $key['public_key'] = self::convertKey($l['href']); + } + } + } + } + + return (($key['public_key']) ? $key : false); + } + + + function get_zotfinger_key($id) { + + $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1", + dbesc(str_replace('acct:','',$id)), + dbesc($id) + ); + if($x && $x[0]['xchan_pubkey']) { + return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; + } - if(array_key_exists('publicKey',$j) && array_key_exists('publicKeyPem',$j['publicKey'])) { - if((array_key_exists('id',$j['publicKey']) && $j['publicKey']['id'] !== $id) && $j['id'] !== $id) - return false; + $wf = Webfinger::exec($id); + $key = [ 'portable_id' => '', 'public_key' => '', 'hubloc' => [] ]; - return($j['publicKey']['publicKeyPem']); + if($wf) { + if(array_key_exists('properties',$wf) && array_key_exists('https://w3id.org/security/v1#publicKeyPem',$wf['properties'])) { + $key['public_key'] = self::convertKey($wf['properties']['https://w3id.org/security/v1#publicKeyPem']); + } + if(array_key_exists('links', $wf) && is_array($wf['links'])) { + foreach($wf['links'] as $l) { + if(! (is_array($l) && array_key_exists('rel',$l))) { + continue; + } + if($l['rel'] === 'http://purl.org/zot/protocol/6.0' && array_key_exists('href',$l) && $l['href'] !== EMPTY_STR) { + $z = \Zotlabs\Lib\Zotfinger::exec($l['href']); + if($z) { + $i = Libzot::import_xchan($z['data']); + if($i['success']) { + $key['portable_id'] = $i['hash']; + + $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_id_url = '%s' limit 1", + dbesc($l['href']) + ); + if($x) { + $key['hubloc'] = $x[0]; + } + } + } + } + if($l['rel'] === 'magic-public-key' && array_key_exists('href',$l) && $key['public_key'] === EMPTY_STR) { + $key['public_key'] = self::convertKey($l['href']); + } + } } } - return false; + return (($key['public_key']) ? $key : false); } + /** * @brief * - * @param string $request * @param array $head * @param string $prvkey - * @param string $keyid (optional, default 'Key') - * @param boolean $send_headers (optional, default false) - * If set send a HTTP header + * @param string $keyid (optional, default '') * @param boolean $auth (optional, default false) * @param string $alg (optional, default 'sha256') - * @param string $crypt_key (optional, default null) - * @param string $crypt_algo (optional, default 'aes256ctr') + * @param array $encryption [ 'key', 'algorithm' ] or false * @return array */ - static function create_sig($request, $head, $prvkey, $keyid = 'Key', $send_headers = false, $auth = false, - $alg = 'sha256', $crypt_key = null, $crypt_algo = 'aes256ctr') { + static function create_sig($head, $prvkey, $keyid = EMPTY_STR, $auth = false, $alg = 'sha256', $encryption = false ) { $return_headers = []; @@ -253,14 +371,15 @@ class HTTPSig { $algorithm = 'rsa-sha512'; } - $x = self::sign($request,$head,$prvkey,$alg); + $x = self::sign($head,$prvkey,$alg); - $headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm - . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; + $headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; - if($crypt_key) { - $x = crypto_encapsulate($headerval,$crypt_key,$crypt_algo); - $headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data'] . '"'; + if($encryption) { + $x = crypto_encapsulate($headerval,$encryption['key'],$encryption['algorithm']); + if(is_array($x)) { + $headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data'] . '"'; + } } if($auth) { @@ -272,43 +391,52 @@ class HTTPSig { if($head) { foreach($head as $k => $v) { - if($send_headers) { - header($k . ': ' . $v); - } - else { - $return_headers[] = $k . ': ' . $v; + // strip the request-target virtual header from the output headers + if($k === '(request-target)') { + continue; } + $return_headers[] = $k . ': ' . $v; } } - if($send_headers) { - header($sighead); - } - else { - $return_headers[] = $sighead; - } + $return_headers[] = $sighead; return $return_headers; } /** + * @brief set headers + * + * @param array $headers + * @return void + */ + + + static function set_headers($headers) { + if($headers && is_array($headers)) { + foreach($headers as $h) { + header($h); + } + } + } + + + /** * @brief * - * @param string $request * @param array $head * @param string $prvkey * @param string $alg (optional) default 'sha256' * @return array */ - static function sign($request, $head, $prvkey, $alg = 'sha256') { + + static function sign($head, $prvkey, $alg = 'sha256') { $ret = []; $headers = ''; $fields = ''; - if($request) { - $headers = '(request-target)' . ': ' . trim($request) . "\n"; - $fields = '(request-target)'; - } + + logger('signing: ' . print_r($head,true), LOGGER_DATA); if($head) { foreach($head as $k => $v) { @@ -340,11 +468,8 @@ class HTTPSig { * - \e array \b headers * - \e string \b signature */ - static function parse_sigheader($header) { - if(is_array($header)) { - btlogger('is_array: ' . print_r($header,true)); - } + static function parse_sigheader($header) { $ret = []; $matches = []; @@ -381,6 +506,7 @@ class HTTPSig { * - \e string \b alg * - \e string \b data */ + static function decrypt_sigheader($header, $prvkey = null) { $iv = $key = $alg = $data = null; diff --git a/Zotlabs/Zot/Finger.php b/Zotlabs/Zot/Finger.php index cb38c7f2b..778b701cd 100644 --- a/Zotlabs/Zot/Finger.php +++ b/Zotlabs/Zot/Finger.php @@ -2,6 +2,8 @@ namespace Zotlabs\Zot; +use Zotlabs\Web\HTTPSig; + /** * @brief Finger * @@ -95,8 +97,7 @@ class Finger { $headers['X-Zot-Nonce'] = random_string(); $headers['Host'] = $parsed_host; - $xhead = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], - 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false); + $xhead = HTTPSig::create_sig($headers,$channel['channel_prvkey'],'acct:' . channel_reddress($channel)); $retries = 0; @@ -129,7 +130,7 @@ class Finger { $x = json_decode($result['body'], true); - $verify = \Zotlabs\Web\HTTPSig::verify($result,(($x) ? $x['key'] : '')); + $verify = HTTPSig::verify($result,(($x) ? $x['key'] : '')); if($x && (! $verify['header_valid'])) { $signed_token = ((is_array($x) && array_key_exists('signed_token', $x)) ? $x['signed_token'] : null); diff --git a/Zotlabs/Zot6/Finger.php b/Zotlabs/Zot6/Finger.php index f1fe41352..22ce4685d 100644 --- a/Zotlabs/Zot6/Finger.php +++ b/Zotlabs/Zot6/Finger.php @@ -88,8 +88,7 @@ class Finger { $headers = []; $headers['X-Zot-Channel'] = $channel['channel_address'] . '@' . \App::get_hostname(); $headers['X-Zot-Nonce'] = random_string(); - $xhead = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], - 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false); + $xhead = HTTPSig::create_sig($headers,$channel['channel_prvkey'],'acct:' . channel_reddress($channel)); $retries = 0; @@ -122,7 +121,7 @@ class Finger { $x = json_decode($result['body'], true); - $verify = \Zotlabs\Web\HTTPSig::verify($result,(($x) ? $x['key'] : '')); + $verify = HTTPSig::verify($result,(($x) ? $x['key'] : '')); if($x && (! $verify['header_valid'])) { $signed_token = ((is_array($x) && array_key_exists('signed_token', $x)) ? $x['signed_token'] : null); diff --git a/Zotlabs/Zot6/HTTPSig.php b/Zotlabs/Zot6/HTTPSig.php deleted file mode 100644 index d3a09b858..000000000 --- a/Zotlabs/Zot6/HTTPSig.php +++ /dev/null @@ -1,536 +0,0 @@ -<?php - -namespace Zotlabs\Zot6; - -use Zotlabs\Lib\ActivityStreams; -use Zotlabs\Lib\Webfinger; -use Zotlabs\Web\HTTPHeaders; -use Zotlabs\Lib\Libzot; - -/** - * @brief Implements HTTP Signatures per draft-cavage-http-signatures-10. - * - * @see https://tools.ietf.org/html/draft-cavage-http-signatures-10 - */ - -class HTTPSig { - - /** - * @brief RFC5843 - * - * @see https://tools.ietf.org/html/rfc5843 - * - * @param string $body The value to create the digest for - * @param string $alg hash algorithm (one of 'sha256','sha512') - * @return string The generated digest header string for $body - */ - - static function generate_digest_header($body,$alg = 'sha256') { - - $digest = base64_encode(hash($alg, $body, true)); - switch($alg) { - case 'sha512': - return 'SHA-512=' . $digest; - case 'sha256': - default: - return 'SHA-256=' . $digest; - break; - } - } - - static function find_headers($data,&$body) { - - // decide if $data arrived via controller submission or curl - - if(is_array($data) && $data['header']) { - if(! $data['success']) - return []; - - $h = new HTTPHeaders($data['header']); - $headers = $h->fetcharr(); - $body = $data['body']; - $headers['(request-target)'] = $data['request_target']; - } - - else { - $headers = []; - $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; - $headers['content-type'] = $_SERVER['CONTENT_TYPE']; - $headers['content-length'] = $_SERVER['CONTENT_LENGTH']; - - foreach($_SERVER as $k => $v) { - if(strpos($k,'HTTP_') === 0) { - $field = str_replace('_','-',strtolower(substr($k,5))); - $headers[$field] = $v; - } - } - } - - //logger('SERVER: ' . print_r($_SERVER,true), LOGGER_ALL); - - //logger('headers: ' . print_r($headers,true), LOGGER_ALL); - - return $headers; - } - - - // See draft-cavage-http-signatures-10 - - static function verify($data,$key = '') { - - $body = $data; - $headers = null; - - $result = [ - 'signer' => '', - 'portable_id' => '', - 'header_signed' => false, - 'header_valid' => false, - 'content_signed' => false, - 'content_valid' => false - ]; - - - $headers = self::find_headers($data,$body); - - if(! $headers) - return $result; - - $sig_block = null; - - if(array_key_exists('signature',$headers)) { - $sig_block = self::parse_sigheader($headers['signature']); - } - elseif(array_key_exists('authorization',$headers)) { - $sig_block = self::parse_sigheader($headers['authorization']); - } - - if(! $sig_block) { - logger('no signature provided.', LOGGER_DEBUG); - return $result; - } - - // Warning: This log statement includes binary data - // logger('sig_block: ' . print_r($sig_block,true), LOGGER_DATA); - - $result['header_signed'] = true; - - $signed_headers = $sig_block['headers']; - if(! $signed_headers) - $signed_headers = [ 'date' ]; - - $signed_data = ''; - foreach($signed_headers as $h) { - if(array_key_exists($h,$headers)) { - $signed_data .= $h . ': ' . $headers[$h] . "\n"; - } - if($h === 'date') { - $d = new \DateTime($headers[$h]); - $d->setTimeZone(new \DateTimeZone('UTC')); - $dplus = datetime_convert('UTC','UTC','now + 1 day'); - $dminus = datetime_convert('UTC','UTC','now - 1 day'); - $c = $d->format('Y-m-d H:i:s'); - if($c > $dplus || $c < $dminus) { - logger('bad time: ' . $c); - return $result; - } - } - } - $signed_data = rtrim($signed_data,"\n"); - - $algorithm = null; - if($sig_block['algorithm'] === 'rsa-sha256') { - $algorithm = 'sha256'; - } - if($sig_block['algorithm'] === 'rsa-sha512') { - $algorithm = 'sha512'; - } - - if(! array_key_exists('keyId',$sig_block)) - return $result; - - $result['signer'] = $sig_block['keyId']; - - $key = self::get_key($key,$result['signer']); - - if(! ($key && $key['public_key'])) { - return $result; - } - - $x = rsa_verify($signed_data,$sig_block['signature'],$key['public_key'],$algorithm); - - logger('verified: ' . $x, LOGGER_DEBUG); - - if(! $x) { - logger('verify failed for ' . $result['signer'] . ' alg=' . $algorithm . (($key['public_key']) ? '' : ' no key')); - $sig_block['signature'] = base64_encode($sig_block['signature']); - logger('affected sigblock: ' . print_r($sig_block,true)); - logger('signed_data: ' . print_r($signed_data,true)); - logger('headers: ' . print_r($headers,true)); - logger('server: ' . print_r($_SERVER,true)); - return $result; - } - - $result['portable_id'] = $key['portable_id']; - $result['header_valid'] = true; - - if(in_array('digest',$signed_headers)) { - $result['content_signed'] = true; - $digest = explode('=', $headers['digest'], 2); - if($digest[0] === 'SHA-256') - $hashalg = 'sha256'; - if($digest[0] === 'SHA-512') - $hashalg = 'sha512'; - - if(base64_encode(hash($hashalg,$body,true)) === $digest[1]) { - $result['content_valid'] = true; - } - - logger('Content_Valid: ' . (($result['content_valid']) ? 'true' : 'false')); - } - - return $result; - } - - static function get_key($key,$id) { - - if($key) { - if(function_exists($key)) { - return $key($id); - } - return [ 'public_key' => $key ]; - } - - if(strpos($id,'#') === false) { - $key = self::get_webfinger_key($id); - } - - if(! $key) { - $key = self::get_activitystreams_key($id); - } - - return $key; - - } - - - function convertKey($key) { - - if(strstr($key,'RSA ')) { - return rsatopem($key); - } - elseif(substr($key,0,5) === 'data:') { - return convert_salmon_key($key); - } - else { - return $key; - } - - } - - - /** - * @brief - * - * @param string $id - * @return boolean|string - * false if no pub key found, otherwise return the pub key - */ - - function get_activitystreams_key($id) { - - // remove fragment - - $url = ((strpos($id,'#')) ? substr($id,0,strpos($id,'#')) : $id); - - $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1", - dbesc(str_replace('acct:','',$url)), - dbesc($url) - ); - - if($x && $x[0]['xchan_pubkey']) { - return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; - } - - $r = ActivityStreams::fetch($id); - - if($r) { - if(array_key_exists('publicKey',$r) && array_key_exists('publicKeyPem',$r['publicKey']) && array_key_exists('id',$r['publicKey'])) { - if($r['publicKey']['id'] === $id || $r['id'] === $id) { - $portable_id = ((array_key_exists('owner',$r['publicKey'])) ? $r['publicKey']['owner'] : EMPTY_STR); - return [ 'public_key' => self::convertKey($r['publicKey']['publicKeyPem']), 'portable_id' => $portable_id, 'hubloc' => [] ]; - } - } - } - return false; - } - - - function get_webfinger_key($id) { - - $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1", - dbesc(str_replace('acct:','',$id)), - dbesc($id) - ); - - if($x && $x[0]['xchan_pubkey']) { - return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; - } - - $wf = Webfinger::exec($id); - $key = [ 'portable_id' => '', 'public_key' => '', 'hubloc' => [] ]; - - if($wf) { - if(array_key_exists('properties',$wf) && array_key_exists('https://w3id.org/security/v1#publicKeyPem',$wf['properties'])) { - $key['public_key'] = self::convertKey($wf['properties']['https://w3id.org/security/v1#publicKeyPem']); - } - if(array_key_exists('links', $wf) && is_array($wf['links'])) { - foreach($wf['links'] as $l) { - if(! (is_array($l) && array_key_exists('rel',$l))) { - continue; - } - if($l['rel'] === 'magic-public-key' && array_key_exists('href',$l) && $key['public_key'] === EMPTY_STR) { - $key['public_key'] = self::convertKey($l['href']); - } - } - } - } - - return (($key['public_key']) ? $key : false); - } - - - function get_zotfinger_key($id) { - - $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1", - dbesc(str_replace('acct:','',$id)), - dbesc($id) - ); - if($x && $x[0]['xchan_pubkey']) { - return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; - } - - $wf = Webfinger::exec($id); - $key = [ 'portable_id' => '', 'public_key' => '', 'hubloc' => [] ]; - - if($wf) { - if(array_key_exists('properties',$wf) && array_key_exists('https://w3id.org/security/v1#publicKeyPem',$wf['properties'])) { - $key['public_key'] = self::convertKey($wf['properties']['https://w3id.org/security/v1#publicKeyPem']); - } - if(array_key_exists('links', $wf) && is_array($wf['links'])) { - foreach($wf['links'] as $l) { - if(! (is_array($l) && array_key_exists('rel',$l))) { - continue; - } - if($l['rel'] === 'http://purl.org/zot/protocol/6.0' && array_key_exists('href',$l) && $l['href'] !== EMPTY_STR) { - $z = \Zotlabs\Lib\Zotfinger::exec($l['href']); - if($z) { - $i = Libzot::import_xchan($z['data']); - if($i['success']) { - $key['portable_id'] = $i['hash']; - - $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_id_url = '%s' limit 1", - dbesc($l['href']) - ); - if($x) { - $key['hubloc'] = $x[0]; - } - } - } - } - if($l['rel'] === 'magic-public-key' && array_key_exists('href',$l) && $key['public_key'] === EMPTY_STR) { - $key['public_key'] = self::convertKey($l['href']); - } - } - } - } - - return (($key['public_key']) ? $key : false); - } - - - /** - * @brief - * - * @param array $head - * @param string $prvkey - * @param string $keyid (optional, default '') - * @param boolean $auth (optional, default false) - * @param string $alg (optional, default 'sha256') - * @param array $encryption [ 'key', 'algorithm' ] or false - * @return array - */ - static function create_sig($head, $prvkey, $keyid = EMPTY_STR, $auth = false, $alg = 'sha256', $encryption = false ) { - - $return_headers = []; - - if($alg === 'sha256') { - $algorithm = 'rsa-sha256'; - } - if($alg === 'sha512') { - $algorithm = 'rsa-sha512'; - } - - $x = self::sign($head,$prvkey,$alg); - - $headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; - - if($encryption) { - $x = crypto_encapsulate($headerval,$encryption['key'],$encryption['algorithm']); - if(is_array($x)) { - $headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data'] . '"'; - } - } - - if($auth) { - $sighead = 'Authorization: Signature ' . $headerval; - } - else { - $sighead = 'Signature: ' . $headerval; - } - - if($head) { - foreach($head as $k => $v) { - // strip the request-target virtual header from the output headers - if($k === '(request-target)') { - continue; - } - $return_headers[] = $k . ': ' . $v; - } - } - $return_headers[] = $sighead; - - return $return_headers; - } - - /** - * @brief set headers - * - * @param array $headers - * @return void - */ - - - static function set_headers($headers) { - if($headers && is_array($headers)) { - foreach($headers as $h) { - header($h); - } - } - } - - - /** - * @brief - * - * @param array $head - * @param string $prvkey - * @param string $alg (optional) default 'sha256' - * @return array - */ - - static function sign($head, $prvkey, $alg = 'sha256') { - - $ret = []; - - $headers = ''; - $fields = ''; - - logger('signing: ' . print_r($head,true), LOGGER_DATA); - - if($head) { - foreach($head as $k => $v) { - $headers .= strtolower($k) . ': ' . trim($v) . "\n"; - if($fields) - $fields .= ' '; - - $fields .= strtolower($k); - } - // strip the trailing linefeed - $headers = rtrim($headers,"\n"); - } - - $sig = base64_encode(rsa_sign($headers,$prvkey,$alg)); - - $ret['headers'] = $fields; - $ret['signature'] = $sig; - - return $ret; - } - - /** - * @brief - * - * @param string $header - * @return array associate array with - * - \e string \b keyID - * - \e string \b algorithm - * - \e array \b headers - * - \e string \b signature - */ - - static function parse_sigheader($header) { - - $ret = []; - $matches = []; - - // if the header is encrypted, decrypt with (default) site private key and continue - - if(preg_match('/iv="(.*?)"/ism',$header,$matches)) - $header = self::decrypt_sigheader($header); - - if(preg_match('/keyId="(.*?)"/ism',$header,$matches)) - $ret['keyId'] = $matches[1]; - if(preg_match('/algorithm="(.*?)"/ism',$header,$matches)) - $ret['algorithm'] = $matches[1]; - if(preg_match('/headers="(.*?)"/ism',$header,$matches)) - $ret['headers'] = explode(' ', $matches[1]); - if(preg_match('/signature="(.*?)"/ism',$header,$matches)) - $ret['signature'] = base64_decode(preg_replace('/\s+/','',$matches[1])); - - if(($ret['signature']) && ($ret['algorithm']) && (! $ret['headers'])) - $ret['headers'] = [ 'date' ]; - - return $ret; - } - - - /** - * @brief - * - * @param string $header - * @param string $prvkey (optional), if not set use site private key - * @return array|string associative array, empty string if failue - * - \e string \b iv - * - \e string \b key - * - \e string \b alg - * - \e string \b data - */ - - static function decrypt_sigheader($header, $prvkey = null) { - - $iv = $key = $alg = $data = null; - - if(! $prvkey) { - $prvkey = get_config('system', 'prvkey'); - } - - $matches = []; - - if(preg_match('/iv="(.*?)"/ism',$header,$matches)) - $iv = $matches[1]; - if(preg_match('/key="(.*?)"/ism',$header,$matches)) - $key = $matches[1]; - if(preg_match('/alg="(.*?)"/ism',$header,$matches)) - $alg = $matches[1]; - if(preg_match('/data="(.*?)"/ism',$header,$matches)) - $data = $matches[1]; - - if($iv && $key && $alg && $data) { - return crypto_unencapsulate([ 'encrypted' => true, 'iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data ] , $prvkey); - } - - return ''; - } - -} diff --git a/Zotlabs/Zot6/Receiver.php b/Zotlabs/Zot6/Receiver.php index ed7c33b3a..9e70ab318 100644 --- a/Zotlabs/Zot6/Receiver.php +++ b/Zotlabs/Zot6/Receiver.php @@ -4,6 +4,7 @@ namespace Zotlabs\Zot6; use Zotlabs\Lib\Config; use Zotlabs\Lib\Libzot; +use Zotlabs\Web\HTTPSig; class Receiver { @@ -468,7 +468,7 @@ define ( 'NAMESPACE_YMEDIA', 'http://search.yahoo.com/mrss/' ); define ( 'ACTIVITYSTREAMS_JSONLD_REV', 'https://www.w3.org/ns/activitystreams' ); -define ( 'ZOT_APSCHEMA_REV', '/apschema/v1.5' ); +define ( 'ZOT_APSCHEMA_REV', '/apschema/v1.7' ); /** * activity stream defines */ diff --git a/include/api_auth.php b/include/api_auth.php index 23ab9c946..9235bd28c 100644 --- a/include/api_auth.php +++ b/include/api_auth.php @@ -96,11 +96,15 @@ function api_login(&$a){ if($sigblock) { $keyId = str_replace('acct:','',$sigblock['keyId']); if($keyId) { - $r = q("select * from hubloc where hubloc_addr = '%s' limit 1", + $r = q("select * from hubloc where ( hubloc_addr = '%s' or hubloc_id_url = '%s' ) limit 1", + dbesc($keyId), dbesc($keyId) ); if($r) { $c = channelx_by_hash($r[0]['hubloc_hash']); + if (! $c) { + $c = channelx_by_portid($r[0]['hubloc_hash']); + } if($c) { $a = q("select * from account where account_id = %d limit 1", intval($c['channel_account_id']) diff --git a/include/api_zot.php b/include/api_zot.php index b332aea71..287720484 100644 --- a/include/api_zot.php +++ b/include/api_zot.php @@ -6,8 +6,8 @@ api_register_func('api/export/basic','api_export_basic', true); api_register_func('api/red/channel/export/basic','api_export_basic', true); api_register_func('api/z/1.0/channel/export/basic','api_export_basic', true); - api_register_func('api/red/item/export/page','api_item_export_page', true); - api_register_func('api/z/1.0/item/export/page','api_item_export_page', true); + api_register_func('api/red/item/export_page','api_item_export_page', true); + api_register_func('api/z/1.0/item/export_page','api_item_export_page', true); api_register_func('api/red/channel/list','api_channel_list', true); api_register_func('api/z/1.0/channel/list','api_channel_list', true); api_register_func('api/red/channel/stream','api_channel_stream', true); diff --git a/include/channel.php b/include/channel.php index e4b6df47b..0280cd1cd 100644 --- a/include/channel.php +++ b/include/channel.php @@ -1161,7 +1161,7 @@ function channel_export_items_date($channel_id, $start, $finish) { $ret['relocate'] = [ 'channel_address' => $ch['channel_address'], 'url' => z_root()]; } - $r = q("select * from item where ( item_wall = 1 or item_type != %d ) and item_deleted = 0 and uid = %d and created >= '%s' and created <= '%s' and resource_type = '' order by created", + $r = q("select * from item where ( item_wall = 1 or item_type != %d ) and item_deleted = 0 and uid = %d and created >= '%s' and created <= '%s' and resource_type != 'photo' order by created", intval(ITEM_TYPE_POST), intval($channel_id), dbesc($start), @@ -1223,7 +1223,7 @@ function channel_export_items_page($channel_id, $start, $finish, $page = 0, $lim $ret['relocate'] = [ 'channel_address' => $ch['channel_address'], 'url' => z_root()]; } - $r = q("select * from item where ( item_wall = 1 or item_type != %d ) and item_deleted = 0 and uid = %d and resource_type = '' and created >= '%s' and created <= '%s' order by created limit %d offset %d", + $r = q("select * from item where ( item_wall = 1 or item_type != %d ) and item_deleted = 0 and uid = %d and resource_type != 'photo' and created >= '%s' and created <= '%s' order by created limit %d offset %d", intval(ITEM_TYPE_POST), intval($channel_id), dbesc($start), diff --git a/include/dir_fns.php b/include/dir_fns.php index 2bd1228ec..08a9fb653 100644 --- a/include/dir_fns.php +++ b/include/dir_fns.php @@ -329,13 +329,36 @@ function update_directory_entry($ud) { if ($ud['ud_addr'] && (! ($ud['ud_flags'] & UPDATE_FLAGS_DELETED))) { $success = false; - $x = zot_finger($ud['ud_addr'], ''); - if ($x['success']) { - $j = json_decode($x['body'], true); - if ($j) - $success = true; - $y = import_xchan($j, 0, $ud); + // directory migration phase 1 (Macgirvin - 29-JUNE-2019) + // fetch zot6 info (if available) as well as historical zot info (if available) + // Once this has been running for > 1 month on the primary directory we can deprecate the historical info and + // modify the directory search to only return zot6 entries, and also modify this function + // to *only* fetch the zot6 entries. + // Otherwise we'll be showing duplicates or have a mostly empty directory for a good chunk of + // the transition period. Directory server load will likely increase "moderately" during this transition. + // The one month counter begins when the primary directory has upgraded to a release which uses this code. + // Hubzilla channels running traditional zot which have not upgraded can or will be dropped from the directory or + // "not found" at the end of the transition period as the directory will only serve zot6 entries at that time. + + $uri = \Zotlabs\Lib\Webfinger::zot_url($ud['ud_addr']); + if($uri) { + $record = \Zotlabs\Lib\Zotfinger::exec($uri); + + // Check the HTTP signature + + $hsig = $record['signature']; + if($hsig && $hsig['signer'] === $url && $hsig['header_valid'] === true && $hsig['content_valid'] === true) { + $x = \Zotlabs\Zot\Libzot::import_xchan($record['data'], 0, $ud); + if($x['success']) { + $success = true; + } + } + } + $x = \Zotlabs\Zot\Finger::run($ud['ud_addr'], ''); + if ($x['success']) { + import_xchan($x, 0, $ud); + $success = true; } if (! $success) { q("update updates set ud_last = '%s' where ud_addr = '%s'", diff --git a/include/import.php b/include/import.php index caf25f5d2..1d3b7c035 100644 --- a/include/import.php +++ b/include/import.php @@ -2,6 +2,8 @@ use Zotlabs\Lib\IConfig; +use Zotlabs\Web\HTTPSig; + require_once('include/menu.php'); require_once('include/perm_upgrade.php'); @@ -1329,7 +1331,7 @@ function sync_files($channel, $files) { $headers = []; $headers['Accept'] = 'application/x-zot+json' ; $headers['Sigtoken'] = random_string(); - $headers = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,true,'sha512'); + $headers = HTTPSig::create_sig($headers,$channel['channel_prvkey'], 'acct:' . channel_reddress($channel),true,'sha512'); $x = z_post_url($fetch_url,$parr,$redirects,[ 'filep' => $fp, 'headers' => $headers]); fclose($fp); @@ -1415,7 +1417,7 @@ function sync_files($channel, $files) { $headers = []; $headers['Accept'] = 'application/x-zot+json' ; $headers['Sigtoken'] = random_string(); - $headers = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,true,'sha512'); + $headers = HTTPSig::create_sig($headers,$channel['channel_prvkey'],'acct:' . channel_reddress($channel),true,'sha512'); $x = z_post_url($fetch_url,$parr,$redirects,[ 'filep' => $fp, 'headers' => $headers]); fclose($fp); diff --git a/include/items.php b/include/items.php index 0af119cc9..4fc659926 100755 --- a/include/items.php +++ b/include/items.php @@ -1988,11 +1988,12 @@ function item_store($arr, $allow_exec = false, $deliver = true) { unset($arr['iconfig']); } - - if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid) || strlen($public_policy)) - $private = 1; - else - $private = $arr['item_private']; + $private = intval($arr['item_private']); + if (! $private) { + if (strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid)) { + $private = 1; + } + } $arr['parent'] = $parent_id; $arr['allow_cid'] = $allow_cid; @@ -2011,7 +2012,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) { // find the item we just created $r = q("SELECT * FROM item WHERE mid = '%s' AND uid = %d and revision = %d ORDER BY id ASC ", - $arr['mid'], // already dbesc'd + dbesc($arr['mid']), intval($arr['uid']), intval($arr['revision']) ); @@ -2032,7 +2033,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) { if(count($r) > 1) { logger('item_store: duplicated post occurred. Removing duplicates.'); q("DELETE FROM item WHERE mid = '%s' AND uid = %d AND id != %d ", - $arr['mid'], + dbesc($arr['mid']), intval($arr['uid']), intval($current_post) ); @@ -4615,12 +4616,12 @@ function set_linkified_perms($linkified, &$str_contact_allow, &$str_group_allow, if(strpos($access_tag,'cid:') === 0) { $str_contact_allow .= '<' . substr($access_tag,4) . '>'; $access_tag = ''; - $private = 1; + $private = 2; } elseif(strpos($access_tag,'gid:') === 0) { $str_group_allow .= '<' . substr($access_tag,4) . '>'; $access_tag = ''; - $private = 1; + $private = 2; } } } diff --git a/include/network.php b/include/network.php index c754625cd..f6992291d 100644 --- a/include/network.php +++ b/include/network.php @@ -1183,12 +1183,12 @@ function discover_by_webbie($webbie, $protocol = '') { */ function webfinger_rfc7033($webbie, $zot = false) { - if(strpos($webbie,'@')) { + if(filter_var($webbie, FILTER_VALIDATE_EMAIL)) { $lhs = substr($webbie,0,strpos($webbie,'@')); $rhs = substr($webbie,strpos($webbie,'@')+1); $resource = urlencode('acct:' . $webbie); } - else { + elseif(filter_var($webbie, FILTER_VALIDATE_URL)) { $m = parse_url($webbie); if($m) { if($m['scheme'] !== 'https') @@ -1197,9 +1197,10 @@ function webfinger_rfc7033($webbie, $zot = false) { $rhs = $m['host'] . (($m['port']) ? ':' . $m['port'] : ''); $resource = urlencode($webbie); } - else - return false; } + else + return false; + logger('fetching url from resource: ' . $rhs . ':' . $webbie); $counter = 0; @@ -1217,7 +1218,7 @@ function webfinger_rfc7033($webbie, $zot = false) { function old_webfinger($webbie) { $host = ''; - if(strstr($webbie,'@')) + if(filter_var($webbie, FILTER_VALIDATE_EMAIL)) $host = substr($webbie,strpos($webbie,'@') + 1); if(strlen($host)) { diff --git a/include/photos.php b/include/photos.php index 7fd712f3e..ee662f707 100644 --- a/include/photos.php +++ b/include/photos.php @@ -261,7 +261,7 @@ function photo_upload($channel, $observer, $args) { $r0 = $ph->save($p); $link[0] = array( 'rel' => 'alternate', - 'type' => 'text/html', + 'type' => $type, 'href' => z_root() . '/photo/' . $photo_hash . '-0.' . $ph->getExt(), 'width' => $width, 'height' => $height @@ -280,7 +280,7 @@ function photo_upload($channel, $observer, $args) { $r1 = $ph->storeThumbnail($p, PHOTO_RES_1024); $link[1] = array( 'rel' => 'alternate', - 'type' => 'text/html', + 'type' => $type, 'href' => z_root() . '/photo/' . $photo_hash . '-1.' . $ph->getExt(), 'width' => $ph->getWidth(), 'height' => $ph->getHeight() @@ -294,7 +294,7 @@ function photo_upload($channel, $observer, $args) { $r2 = $ph->storeThumbnail($p, PHOTO_RES_640); $link[2] = array( 'rel' => 'alternate', - 'type' => 'text/html', + 'type' => $type, 'href' => z_root() . '/photo/' . $photo_hash . '-2.' . $ph->getExt(), 'width' => $ph->getWidth(), 'height' => $ph->getHeight() @@ -308,7 +308,7 @@ function photo_upload($channel, $observer, $args) { $r3 = $ph->storeThumbnail($p, PHOTO_RES_320); $link[3] = array( 'rel' => 'alternate', - 'type' => 'text/html', + 'type' => $type, 'href' => z_root() . '/photo/' . $photo_hash . '-3.' . $ph->getExt(), 'width' => $ph->getWidth(), 'height' => $ph->getHeight() diff --git a/include/socgraph.php b/include/socgraph.php index 6cddbbaac..3d26f5cfd 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -1,5 +1,10 @@ <?php /** @file */ + +use Zotlabs\Lib\Libzot; +use Zotlabs\Lib\Libzotdir; +use Zotlabs\Lib\Zotfinger; + require_once('include/dir_fns.php'); require_once('include/zot.php'); @@ -122,7 +127,7 @@ function poco_load($xchan = '', $url = null) { $profile_url = $url['value']; continue; } - if($url['type'] == 'zot') { + if(in_array($url['type'], ['zot','zot6'] )) { $network = $url['type']; $address = str_replace('acct:' , '', $url['value']); continue; @@ -151,6 +156,18 @@ function poco_load($xchan = '', $url = null) { if(($x !== false) && (! count($x))) { if($address) { + if($network === 'zot6') { + $j = Zotfinger::exec($profile_url); + if(is_array($j) && array_path_exists('signature/signer',$j) && $j['signature']['signer'] === $profile_url && intval($j['signature']['header_valid'])) { + Libzot::import_xchan($j['data']); + } + $x = q("select xchan_hash from xchan where xchan_hash = '%s' limit 1", + dbesc($hash) + ); + if(! $x) { + continue; + } + } if($network === 'zot') { $j = Zotlabs\Zot\Finger::run($address,null); if($j['success']) { @@ -402,7 +419,7 @@ function poco($a,$extended = false) { $sql_extra ", intval($channel_id) ); - $rooms = q("select * from menu_item where ( mitem_flags & " . intval(MENU_ITEM_CHATROOM) . " )>0 and allow_cid = '' and allow_gid = '' and deny_cid = '' and deny_gid = '' and mitem_channel_id = %d", + $rooms = q("select * from menu_item where ( mitem_flags & " . intval(MENU_ITEM_CHATROOM) . " ) > 0 and allow_cid = '' and allow_gid = '' and deny_cid = '' and deny_gid = '' and mitem_channel_id = %d", intval($channel_id) ); } diff --git a/include/xchan.php b/include/xchan.php index 4fcdf9fce..d69d707aa 100644 --- a/include/xchan.php +++ b/include/xchan.php @@ -1,6 +1,6 @@ <?php -use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Web\HTTPSig; use Zotlabs\Lib\Libzot; diff --git a/include/zot.php b/include/zot.php index 5fd900765..53c3d4d86 100644 --- a/include/zot.php +++ b/include/zot.php @@ -303,9 +303,8 @@ function zot_zot($url, $data, $channel = null,$crypto = null) { if($channel) { $headers['X-Zot-Token'] = random_string(); - $hash = \Zotlabs\Web\HTTPSig::generate_digest($data,false); - $headers['X-Zot-Digest'] = 'SHA-256=' . $hash; - $h = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'],'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,false,'sha512',(($crypto) ? $crypto['hubloc_sitekey'] : ''), (($crypto) ? zot_best_algorithm($crypto['site_crypto']) : '')); + $headers['X-Zot-Digest'] = \Zotlabs\Web\HTTPSig::generate_digest_header($data); + $h = \Zotlabs\Web\HTTPSig::create_sig($headers,$channel['channel_prvkey'],'acct:' . channel_reddress($channel),false,'sha512',(($crypto) ? $crypto['hubloc_sitekey'] : ''), (($crypto) ? zot_best_algorithm($crypto['site_crypto']) : '')); } $redirects = 0; diff --git a/install/INSTALL.txt b/install/INSTALL.txt index 0503ae2cc..b6014c160 100644 --- a/install/INSTALL.txt +++ b/install/INSTALL.txt @@ -83,7 +83,7 @@ web server platforms. Example config scripts are available for these platforms in the install directory. Apache and nginx have the most support. - - PHP 5.6 or later. + - PHP 7.1 or later. - PHP *command line* access with register_argc_argv set to true in the php.ini file - and with no hosting provider restrictions on the use of diff --git a/tests/unit/Web/HttpSigTest.php b/tests/unit/Web/HttpSigTest.php index 9909a9883..db0f9700f 100644 --- a/tests/unit/Web/HttpSigTest.php +++ b/tests/unit/Web/HttpSigTest.php @@ -43,45 +43,30 @@ class PermissionDescriptionTest extends UnitTestCase { function testGenerate_digest($text, $digest) { $this->assertSame( $digest, - HTTPSig::generate_digest($text, false) + HTTPSig::generate_digest_header($text) ); } public function generate_digestProvider() { return [ 'empty body text' => [ '', - '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + 'SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' ], 'sample body text' => [ 'body text', - '2fu8kUkvuzuo5XyhWwORNOcJgDColXgxWkw1T5EXzPI=' + 'SHA-256=2fu8kUkvuzuo5XyhWwORNOcJgDColXgxWkw1T5EXzPI=' ], 'NULL body text' => [ null, - '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + 'SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' ], ]; } function testGeneratedDigestsOfDifferentTextShouldNotBeEqual() { $this->assertNotSame( - HTTPSig::generate_digest('text1', false), - HTTPSig::generate_digest('text2', false) - ); - } - - /** - * Process separation needed for header() check. - * @runInSeparateProcess - */ - function testGenerate_digestSendsHttpHeader() { - $ret = HTTPSig::generate_digest('body text', true); - - $this->assertSame('2fu8kUkvuzuo5XyhWwORNOcJgDColXgxWkw1T5EXzPI=', $ret); - $this->assertContains( - 'Digest: SHA-256=2fu8kUkvuzuo5XyhWwORNOcJgDColXgxWkw1T5EXzPI=', - xdebug_get_headers(), - 'HTTP header Digest does not match' + HTTPSig::generate_digest_header('text1'), + HTTPSig::generate_digest_header('text2') ); } diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index a29d9226b..c006debcd 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -1431,7 +1431,6 @@ return array( 'Zotlabs\\Widget\\Wiki_pages' => $baseDir . '/Zotlabs/Widget/Wiki_pages.php', 'Zotlabs\\Widget\\Zcard' => $baseDir . '/Zotlabs/Widget/Zcard.php', 'Zotlabs\\Zot6\\Finger' => $baseDir . '/Zotlabs/Zot6/Finger.php', - 'Zotlabs\\Zot6\\HTTPSig' => $baseDir . '/Zotlabs/Zot6/HTTPSig.php', 'Zotlabs\\Zot6\\IHandler' => $baseDir . '/Zotlabs/Zot6/IHandler.php', 'Zotlabs\\Zot6\\Receiver' => $baseDir . '/Zotlabs/Zot6/Receiver.php', 'Zotlabs\\Zot6\\Zot6Handler' => $baseDir . '/Zotlabs/Zot6/Zot6Handler.php', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 4f5b26949..2c9c7dd96 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -1599,7 +1599,6 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d 'Zotlabs\\Widget\\Wiki_pages' => __DIR__ . '/../..' . '/Zotlabs/Widget/Wiki_pages.php', 'Zotlabs\\Widget\\Zcard' => __DIR__ . '/../..' . '/Zotlabs/Widget/Zcard.php', 'Zotlabs\\Zot6\\Finger' => __DIR__ . '/../..' . '/Zotlabs/Zot6/Finger.php', - 'Zotlabs\\Zot6\\HTTPSig' => __DIR__ . '/../..' . '/Zotlabs/Zot6/HTTPSig.php', 'Zotlabs\\Zot6\\IHandler' => __DIR__ . '/../..' . '/Zotlabs/Zot6/IHandler.php', 'Zotlabs\\Zot6\\Receiver' => __DIR__ . '/../..' . '/Zotlabs/Zot6/Receiver.php', 'Zotlabs\\Zot6\\Zot6Handler' => __DIR__ . '/../..' . '/Zotlabs/Zot6/Zot6Handler.php', diff --git a/view/tpl/cdav_calendar.tpl b/view/tpl/cdav_calendar.tpl index 083c7cea3..01739dd5b 100644 --- a/view/tpl/cdav_calendar.tpl +++ b/view/tpl/cdav_calendar.tpl @@ -39,13 +39,26 @@ $(document).ready(function() { defaultView: default_view, defaultDate: default_date, + weekNumbers: true, + navLinks: true, + + navLinkDayClick: function(date, jsEvent) { + calendar.gotoDate( date ); + changeView('timeGridDay'); + }, + + navLinkWeekClick: function(date, jsEvent) { + calendar.gotoDate( date ); + changeView('timeGridWeek'); + }, + monthNames: aStr['monthNames'], monthNamesShort: aStr['monthNamesShort'], dayNames: aStr['dayNames'], dayNamesShort: aStr['dayNamesShort'], allDayText: aStr['allday'], - snapDuration: '00:15:00', + snapDuration: '00:05:00', dateClick: function(info) { if(new_event.id) { @@ -56,6 +69,14 @@ $(document).ready(function() { allday = info.allDay; + if(allday) { + $('#id_dtstart_wrapper, #id_dtend_wrapper, #id_timezone_select_wrapper').hide(); + } + else { + $('#id_dtstart_wrapper, #id_dtend_wrapper, #id_timezone_select_wrapper').show(); + } + + var dtend = new Date(info.date.toUTCString()); if(allday) { dtend.setDate(dtend.getDate() + 1); @@ -72,14 +93,14 @@ $(document).ready(function() { $('#id_description').attr('disabled', false); $('#id_location').attr('disabled', false); $('#calendar_select').val($("#calendar_select option:first").val()).attr('disabled', false); - $('#id_dtstart').val(info.date.toUTCString()); - $('#id_dtend').val(dtend ? dtend.toUTCString() : ''); + $('#id_dtstart').val(info.date.toUTCString().slice(0, -4)); + $('#id_dtend').val(dtend ? dtend.toUTCString().slice(0, -4) : ''); $('#id_description').val(''); $('#id_location').val(''); $('#event_submit').val('create_event').html('{{$create}}'); $('#event_delete').hide(); - new_event = { id: new_event_id, title: 'New event', start: $('#id_dtstart').val(), end: $('#id_dtend').val(), allDay: info.allDay, editable: true, color: '#bbb' }; + new_event = { id: new_event_id, title: 'New event', start: info.date.toUTCString(), end: dtend ? dtend.toUTCString() : '', allDay: info.allDay, editable: true, color: '#bbb' }; calendar.addEvent(new_event); }, @@ -101,6 +122,13 @@ $(document).ready(function() { $('#l2s').remove(); } + if(event.allDay) { + $('#id_dtstart_wrapper, #id_dtend_wrapper, #id_timezone_select_wrapper').hide(); + } + else { + $('#id_dtstart_wrapper, #id_dtend_wrapper, #id_timezone_select_wrapper').show(); + } + if(event.publicId == new_event_id) { $('#calendar_select').trigger('change'); $('#event_submit').show(); @@ -136,8 +164,8 @@ $(document).ready(function() { $('#id_timezone_select').val(event.extendedProps.timezone); $('#id_location').val(event.extendedProps.location); $('#id_categories').tagsinput('add', event.extendedProps.categories); - $('#id_dtstart').val(dtstart.toUTCString()); - $('#id_dtend').val(dtend.toUTCString()); + $('#id_dtstart').val(dtstart.toUTCString().slice(0, -4)); + $('#id_dtend').val(dtend.toUTCString().slice(0, -4)); $('#id_description').val(event.extendedProps.description); $('#id_location').val(event.extendedProps.location); $('#event_submit').val('update_event').html('{{$update}}'); @@ -183,15 +211,14 @@ $(document).ready(function() { }, eventResize: function(info) { - console.log(info); var event = info.event._def; var dtstart = new Date(info.event._instance.range.start); var dtend = new Date(info.event._instance.range.end); $('#id_title').val(event.title); - $('#id_dtstart').val(dtstart.toUTCString()); - $('#id_dtend').val(dtend.toUTCString()); + $('#id_dtstart').val(dtstart.toUTCString().slice(0, -4)); + $('#id_dtend').val(dtend.toUTCString().slice(0, -4)); event_id = event.extendedProps.item ? event.extendedProps.item.id : 0; event_xchan = event.extendedProps.item ? event.extendedProps.item.event_xchan : ''; @@ -205,8 +232,8 @@ $(document).ready(function() { 'preview': 0, 'summary': event.title, 'timezone_select': event.extendedProps.timezone, - 'dtstart': dtstart.toUTCString(), - 'dtend': dtend.toUTCString(), + 'dtstart': dtstart.toUTCString().slice(0, -4), + 'dtend': dtend.toUTCString().slice(0, -4), 'adjust': event.allDay ? 0 : 1, 'categories': event.extendedProps.categories, 'desc': event.extendedProps.description, @@ -222,8 +249,8 @@ $(document).ready(function() { 'id[]': event.extendedProps.calendar_id, 'uri': event.extendedProps.uri, 'timezone_select': event.extendedProps.timezone, - 'dtstart': dtstart ? dtstart.toUTCString() : '', - 'dtend': dtend ? dtend.toUTCString() : '', + 'dtstart': dtstart ? dtstart.toUTCString().slice(0, -4) : '', + 'dtend': dtend ? dtend.toUTCString().slice(0, -4) : '', 'allday': event.allDay ? 1 : 0 }) .fail(function() { @@ -239,8 +266,8 @@ $(document).ready(function() { var dtend = new Date(info.event._instance.range.end); $('#id_title').val(event.title); - $('#id_dtstart').val(dtstart.toUTCString()); - $('#id_dtend').val(dtend.toUTCString()); + $('#id_dtstart').val(dtstart.toUTCString().slice(0, -4)); + $('#id_dtend').val(dtend.toUTCString().slice(0, -4)); event_id = event.extendedProps.item ? event.extendedProps.item.id : 0; event_xchan = event.extendedProps.item ? event.extendedProps.item.event_xchan : ''; @@ -254,8 +281,8 @@ $(document).ready(function() { 'preview': 0, 'summary': event.title, 'timezone_select': event.extendedProps.timezone, - 'dtstart': dtstart.toUTCString(), - 'dtend': dtend.toUTCString(), + 'dtstart': dtstart.toUTCString().slice(0, -4), + 'dtend': dtend.toUTCString().slice(0, -4), 'adjust': event.allDay ? 0 : 1, 'categories': event.extendedProps.categories, 'desc': event.extendedProps.description, @@ -271,8 +298,8 @@ $(document).ready(function() { 'id[]': event.extendedProps.calendar_id, 'uri': event.extendedProps.uri, 'timezone_select': event.extendedProps.timezone, - 'dtstart': dtstart ? dtstart.toUTCString() : '', - 'dtend': dtend ? dtend.toUTCString() : '', + 'dtstart': dtstart ? dtstart.toUTCString().slice(0, -4) : '', + 'dtend': dtend ? dtend.toUTCString().slice(0, -4) : '', 'allday': event.allDay ? 1 : 0 }) .fail(function() { @@ -340,8 +367,8 @@ $(document).ready(function() { $('#calendar_select').val('channel_calendar').attr('disabled', true); $('#id_title').val(resource.summary); - $('#id_dtstart').val(new Date(resource.dtstart).toUTCString()); - $('#id_dtend').val(new Date(resource.dtend).toUTCString()); + $('#id_dtstart').val(new Date(resource.dtstart).toUTCString().slice(0, -4)); + $('#id_dtend').val(new Date(resource.dtend).toUTCString().slice(0, -4)); $('#id_categories').tagsinput('add', '{{$categories}}'), $('#id_description').val(resource.description); $('#id_location').val(resource.location); @@ -352,13 +379,25 @@ $(document).ready(function() { else $('#event_submit').html('{{$update}}'); } + + if(default_view === 'dayGridMonth'); + $('#id_dtstart_wrapper, #id_dtend_wrapper, #id_timezone_select_wrapper').hide(); }); -function changeView(action, viewName) { +function changeView(viewName) { + calendar.changeView(viewName); $('#title').text(calendar.view.title); $('#view_selector').html(views[calendar.view.type]); + + if(viewName === 'dayGridMonth') { + $('#id_dtstart_wrapper, #id_dtend_wrapper, #id_timezone_select_wrapper').hide(); + } + else { + $('#id_dtstart_wrapper, #id_dtend_wrapper, #id_timezone_select_wrapper').show(); + } + return; } @@ -538,13 +577,13 @@ function exportDate() { <div class="dropdown"> <button id="view_selector" type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-toggle="dropdown"></button> <div class="dropdown-menu"> - <a class="dropdown-item" href="#" onclick="changeView('changeView', 'dayGridMonth'); return false;">{{$month}}</a></li> - <a class="dropdown-item" href="#" onclick="changeView('changeView', 'timeGridWeek'); return false;">{{$week}}</a></li> - <a class="dropdown-item" href="#" onclick="changeView('changeView', 'timeGridDay'); return false;">{{$day}}</a></li> + <a class="dropdown-item" href="#" onclick="changeView('dayGridMonth'); return false;">{{$month}}</a></li> + <a class="dropdown-item" href="#" onclick="changeView('timeGridWeek'); return false;">{{$week}}</a></li> + <a class="dropdown-item" href="#" onclick="changeView('timeGridDay'); return false;">{{$day}}</a></li> <div class="dropdown-divider"></div> - <a class="dropdown-item" href="#" onclick="changeView('changeView', 'listMonth'); return false;">{{$list_month}}</a></li> - <a class="dropdown-item" href="#" onclick="changeView('changeView', 'listWeek'); return false;">{{$list_week}}</a></li> - <a class="dropdown-item" href="#" onclick="changeView('changeView', 'listDay'); return false;">{{$list_day}}</a></li> + <a class="dropdown-item" href="#" onclick="changeView('listMonth'); return false;">{{$list_month}}</a></li> + <a class="dropdown-item" href="#" onclick="changeView('listWeek'); return false;">{{$list_week}}</a></li> + <a class="dropdown-item" href="#" onclick="changeView('listDay'); return false;">{{$list_day}}</a></li> </div> <div class="btn-group"> <button id="prev-btn" class="btn btn-outline-secondary btn-sm" title="{{$prev}}"><i class="fa fa-backward"></i></button> diff --git a/view/tpl/field_select_grouped.tpl b/view/tpl/field_select_grouped.tpl index e6d1479de..ec067b8e7 100644 --- a/view/tpl/field_select_grouped.tpl +++ b/view/tpl/field_select_grouped.tpl @@ -1,4 +1,4 @@ - <div class='form-group field select'> + <div id='id_{{$field.0}}_wrapper' class='form-group field select'> <label for='id_{{$field.0}}'>{{$field.1}}</label> <select class="form-control" name='{{$field.0}}' id='id_{{$field.0}}'> {{foreach $field.4 as $group=>$opts}} |